如何模拟对象工厂 [英] How to mock an Object Factory
问题描述
我使用工厂(请参阅 http://www.php.net/manual/zh-CN/language.oop5.patterns.php (针对该模式))可以极大地提高我们代码的可测试性.一个简单的工厂可能看起来像这样:
I use Factories (see http://www.php.net/manual/en/language.oop5.patterns.php for the pattern) a lot to increase the testability of our code. A simple factory could look like this:
class Factory
{
public function getInstanceFor($type)
{
switch ($type) {
case 'foo':
return new Foo();
case 'bar':
return new Bar();
}
}
}
这是使用该工厂的示例类:
Here is a sample class using that factory:
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 */
/* ... */
}
}
现在,为了进行适当的单元测试,我需要模拟将返回类的存根的对象,这就是我遇到的问题.我认为可以这样做:
Now for proper unit testing I need to mock the object that will return stubs for the classes, and that is where I got stuck. I thought it would be possible to do it like this:
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));
}
}
但是当我运行测试时,这就是我得到的:
But when I run the test, this is what I get:
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.
因此,很显然,无法以这种方式根据传递的方法参数让模拟对象返回不同的值.
So obviously it is not possible to let a mock object return different values depending on the passed method arguments this way.
这怎么办?
推荐答案
问题是PHPUnit Mocking不允许您这样做:
The problem is that the PHPUnit Mocking doesn't allow you to do this:
$factoryMock->expects($this->any())
->method('getInstanceFor')
->with('foo')
->will($this->returnValue($fooStub));
$factoryMock->expects($this->any())
->method('getInstanceFor')
->with('bar')
->will($this->returnValue($barStub));
每个-> method();
只能有一个期望
.尚不了解-> with()
的参数是否不同!
You can only have one expects
per ->method();
. It is not aware of the fact that the parameters to ->with()
differ!
因此,您只需将第二个-> expects()
覆盖即可.这就是这些断言的实现方式,而不是人们所期望的.但是有解决方法.
So you just overwrite the first ->expects()
with the second one. It's how those assertions are implemented and it's not what one would expect. But there are workarounds.
您需要用两种行为/返回值定义一个期望!
You need to define one expects with both behaviors / return values!
请参阅: PHPUnit中的模拟-具有不同参数的同一方法的多个配置
在使示例适应您的问题时,它看起来可能像这样:
When adapting the example to your problem it could look like this:
$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;
}
));
这篇关于如何模拟对象工厂的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!