当我用PHPUnit对php代码进行单元测试时,我试图找出正确的方法来模拟对象,而不模拟它的任何方法。
问题是,如果我不调用getMockBuilder()->setMethods()
,那么对象上的所有方法都将被嘲笑,我无法调用我想要测试的方法;但是如果我确实调用setMethods()
,那么我需要告诉它要模拟什么方法,但我根本不想模拟任何方法。但我需要创建mock,这样我就可以避免在测试中调用构造函数。
下面是一个我想测试的方法的琐碎例子:
class Foobar
{
public function __construct()
{
// stuff happens here ...
}
public function myMethod($s)
{
// I want to test this
return (strlen($s) > 3);
}
}
我可以用测试myMethod()
$obj = new Foobar();
$this->assertTrue($obj->myMethod('abcd'));
但这会调用Foobar的构造函数,我不想要它。所以我会尝试:
$obj = $this->getMockBuilder('Foobar')->disableOriginalConstructor()->getMock();
$this->assertTrue($obj->myMethod('abcd'));
但是在不使用setMethods()
的情况下调用getMockBuilder()
会导致它的所有方法都被嘲笑并返回null,所以我对myMethod()
的调用将返回null,而不会接触到我想要测试的代码。
到目前为止,我的解决方法是:
$obj = $this->getMockBuilder('Foobar')->setMethods(array('none'))
->disableOriginalConstructor()->getMock();
$this->assertTrue($obj->myMethod('abcd'));
这将模拟一个名为"none"的方法,该方法不存在,但PHPUnit并不关心。它将使myMethod()处于未锁定状态,以便我可以调用它,还将使我禁用构造函数,以便我不调用它。完美!除了必须指定一个不存在的方法名("none"、"blargh"或"xyzzy")似乎是作弊。
做这件事的正确方法是什么?
您可以将null
传递给setMethods()
以避免嘲弄任何方法。传递一个空数组将模拟所有方法。后者是您找到的方法的默认值。
话虽如此,我想说,这样做的必要性可能会指出这个类的设计中的一个缺陷。应该将此方法设置为静态还是移动到另一个类?如果该方法不需要一个完全构造的实例,那么对我来说,这是一个迹象,表明它可能是一个可以变为静态的实用方法。
另一个技巧但简洁的解决方案是简单地将魔术构造函数列为模拟方法之一:
$mock = $this->getMock('MyClass', array('__construct'));
如果可以使用任何方法,例如通过entityManager调用的方法,则可以使用以下方法:
$this->entityManager
->expects($this->any())
->method($this->anything())
->willReturn($repository);
对于较新版本的PHPUnit,由于setMethods
函数已弃用,因此可以用onlyMethods([])
替换setMethods(array('none'))
/setMethods(null)
或者您可以直接使用getMock()
。
$mock = $this->getMock('MyClass', null, array(), null, false);
这对我有效:
$this->getMock($class, array(), array(), '', false);