我使用Factories(请参阅http://www.php.net/manual/en/language.oop5.patterns.php获取模式)来提高代码的可测试性。一个简单的工厂看起来像这样:
class Factory
{
public function getInstanceFor($type)
{
switch ($type) {
case 'foo':
return new Foo();
case 'bar':
return new Bar();
}
}
}
以下是使用该工厂的示例类:
class Sample
{
protected $_factory;
public function __construct(Factory $factory)
{
$this->_factory = $factory;
}
public function doSomething()
{
$foo = $this->_factory->getInstanceFor('foo');
$bar = $this->_factory->getInstanceFor('bar');
/* more stuff done here */
/* ... */
}
}
现在进行正确的单元测试我需要模拟将返回类的存根的对象,这就是我遇到的问题。我认为可以这样做:
class SampleTest extends PHPUnit_Framework_TestCase
{
public function testAClassUsingObjectFactory()
{
$fooStub = $this->getMock('Foo');
$barStub = $this->getMock('Bar');
$factoryMock = $this->getMock('Factory');
$factoryMock->expects($this->any())
->method('getInstanceFor')
->with('foo')
->will($this->returnValue($fooStub));
$factoryMock->expects($this->any())
->method('getInstanceFor')
->with('bar')
->will($this->returnValue($barStub));
}
}
但是当我进行测试时,这就是我得到的:
F
Time: 0 seconds, Memory: 5.25Mb
There was 1 failure:
1) SampleTest::testDoSomething
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-bar
+foo
FAILURES!
Tests: 1, Assertions: 0, Failures: 1.
显然,不可能让mock对象根据传递的方法参数返回不同的值。
如何做到这一点?
答案 0 :(得分:6)
问题是PHPUnit Mocking不允许你这样做:
$factoryMock->expects($this->any())
->method('getInstanceFor')
->with('foo')
->will($this->returnValue($fooStub));
$factoryMock->expects($this->any())
->method('getInstanceFor')
->with('bar')
->will($this->returnValue($barStub));
每expects
只能有一个->method();
。它不知道->with()
的参数不同!
所以你只需用第二个覆盖第一个->expects()
。这些断言是如何实现的,并不是人们所期望的。但是有一些解决方法。
您需要使用行为/返回值定义一个期望!
请参阅:Mock in PHPUnit - multiple configuration of the same method with different arguments
在将示例调整为您的问题时,它可能如下所示:
$fooStub = $this->getMock('Foo');
$barStub = $this->getMock('Bar');
$factoryMock->expects($this->exactly(2))
->method('getInstanceFor')
->with($this->logicalOr(
$this->equalTo('foo'),
$this->equalTo('bar')
))
->will($this->returnCallback(
function($param) use ($fooStub, $barStub) {
if($param == 'foo') return $fooStub;
return $barStub;
}
));
答案 1 :(得分:1)
创建一个简单的存根工厂类,其构造函数接受应该返回的实例。
class StubFactory extends Factory
{
private $items;
public function __construct(array $items)
{
$this->items = $items;
}
public function getInstanceFor($type)
{
if (!isset($this->items[$type])) {
throw new InvalidArgumentException("Object for $type not found.");
}
return $this->items[$type];
}
}
您可以在任何单元测试中重复使用此类。
class SampleTest extends PHPUnit_Framework_TestCase
{
public function testAClassUsingObjectFactory()
{
$fooStub = $this->getMock('Foo');
$barStub = $this->getMock('Bar');
$factory = new StubFactory(array(
'foo' => $fooStub,
'bar' => $barStub,
));
...no need to set expectations on $factory...
}
}
为了完整起见,如果您不介意编写脆弱的测试,则可以在原始代码中使用at($index)
代替any()
。 如果被测系统更改了出厂调用的顺序或次数,这将会中断,但这很容易编写。
$factoryMock->expects($this->at(0))
->method('getInstanceFor')
->with('foo')
->will($this->returnValue($fooStub));
$factoryMock->expects($this->at(1))
->method('getInstanceFor')
->with('bar')
->will($this->returnValue($barStub));
答案 2 :(得分:0)
你应该改变你的“业务逻辑”...我的意思是你不必将Factory传递给Sample构造函数,你必须传递你需要的确切参数