我正在编写现有代码的单元测试,就像这样
class someClass {
public function __construct() { ... }
public function someFoo($var) {
...
$var = "something";
...
$model = new someClass();
model->someOtherFoo($var);
}
public someOtherFoo($var){
// some code which has to be mocked
}
}
在这里,我应该如何模拟对“someOtherFoo
”函数的调用,使其在some code
内不执行“someOtherFoo
”?
class someClassTest {
public function someFoo() {
$fixture = $this->getMock('someClass ', array('someOtherFoo'));
$var = "something";
....
// How to mock the call to someOtherFoo() here
}
}
是否可以模拟构造函数,以便返回我自己构造的函数或变量?
由于
答案 0 :(得分:28)
无论您在被测方法中有new XXX(...)
,您都注定要失败。将实例化提取到同一类的新方法 - createSomeClass(...)
。这允许您创建一个被测试类的部分模拟,它从新方法返回一个stubbed或mock值。
class someClass {
public function someFoo($var) {
$model = $this->createSomeClass(); // call method instead of using new
model->someOtherFoo($var);
}
public function createSomeClass() { // now you can mock this method in the test
return new someClass();
}
public function someOtherFoo($var){
// some code which has to be mocked
}
}
在测试中,在您调用createSomeClass()
的实例中模拟someFoo()
,并在您从第一次模拟调用返回的实例中模拟someOtherFoo()
。
function testSomeFoo() {
// mock someOtherFoo() to ensure it gets the correct value for $arg
$created = $this->getMock('someClass', array('someOtherFoo'));
$created->expects($this->once())
->method('someOtherFoo')
->with('foo');
// mock createSomeClass() to return the mock above
$creator = $this->getMock('someClass', array('createSomeClass'));
$creator->expects($this->once())
->method('createSomeClass')
->will($this->returnValue($created));
// call someFoo() with the correct $arg
$creator->someFoo('foo');
}
请记住,因为实例正在创建同一个类的另一个实例,所以通常会涉及两个实例。如果它更清晰,你可以在这里使用相同的模拟实例。
function testSomeFoo() {
$fixture = $this->getMock('someClass', array('createSomeClass', 'someOtherFoo'));
// mock createSomeClass() to return the mock
$fixture->expects($this->once())
->method('createSomeClass')
->will($this->returnValue($fixture));
// mock someOtherFoo() to ensure it gets the correct value for $arg
$fixture->expects($this->once())
->method('someOtherFoo')
->with('foo');
// call someFoo() with the correct $arg
$fixture->someFoo('foo');
}
答案 1 :(得分:6)
您可以在模拟类名称前加上overload:
查看Mocking Hard Dependencies上的文档。
您的示例如下:
/**
* @runTestsInSeparateProcesses
* @preserveGlobalState disabled
*/
class SomeClassTest extends \PHPUnit\Framework\TestCase
{
public function test_some_foo()
{
$someOtherClassMock = \Mockery::mock('overload:SomeOtherClass');
$someOtherClassMock->shouldReceive('someOtherFoo')
->once()
->with('something')
->andReturn();
$systemUnderTest = new SomeClass();
$systemUnderTest->someFoo('something');
}
}
我添加了@runTestsInSeparateProcesses
注释,因为通常模拟类也将在其他测试中使用。如果没有注释,则自动加载器将由于class already exists
错误而崩溃。
如果这是在测试套件中唯一使用模拟类的地方,则应删除注释。
答案 2 :(得分:2)
我在这里试图通过白盒测试类__constructor来确保它自己调用一个类方法,并将一些数据传入__constructor。
如果其他人出于同样的原因在这里,我想我会分享我最终使用的方法(没有在这个问题中使用的工厂风格的createSomeClass()方法)。
<?php
class someClass {
public function __constructor($param1) {
// here is the method in the constructor we want to call
$this->someOtherFoo($param1);
}
public function someOtherFoo($var){ }
}
现在PHPUnit测试:
<?php
$paramData = 'someData';
// set up the mock class here
$model = $this->getMock('someClass',
array('someOtherFoo'), // override the method we want to check
array($paramData) // we need to pass in a parameter to the __constructor
);
// test that someOtherFoo() is called once, with out test data
$model->expects($this->once())
->with($paramData)
->method('someOtherFoo');
// directly call the constructor, instead of doing "new someClass" like normal
$model->__construct($paramData);