我正在编写一个PHPUnit测试,我需要模拟一些依赖项,但我需要一些方法,它仍然像以前一样工作。即,我有:
class Dependency {
// some stuff not important for the test
public function thisOneINeed() {
/// complex code
}
// some more stuff
}
所以我做了这样的事情:
// prepare mock object
$dep = $this->getMockBuilder('Dependency')->disableOriginalConstructor()->getMock();
// mock out some other method that should return fixed value
$dep->expects($this->any())->method("shouldGetTrue")->will($this->returnValue(true));
// run test code, it will use thisOneINeed() and shouldGetTrue()
$result = $testSubject->runSomeCode($dep);
$this->assertEquals($expected, $result);
一切都很好,除非方法thisOneINeed()
被模拟掉,所以我没有得到复杂的代码来运行,我需要它来运行runSomeCode()
才能正常工作。 thisOneINeed()
中的代码不会调用任何其他方法,但它需要正确的测试,并且它不会返回固定值,所以我不能只将静态returnValue()放在那里。并且AFAIK PHPunit没有像returnValue()
这样的方法说“call parent”。它有returnCallback()
,但就我所见,没有办法告诉它“为父类调用这个方法”。
我可以在Dependency
中列出所有方法的列表,从中删除thisOneINeed
并在构建模拟时将其传递给setMethods()
,但我不喜欢这种方法,看起来缺憾。
我也可以这样做:
class MockDependency extends Dependency
{
// do not let the mock kill thisOneINeed function
final public function thisOneINeed()
{
return parent::thisOneINeed();
}
}
然后使用MockDependency
来构建模拟对象,这也有效,但我不喜欢手动进行模拟。
那么有更好的方法吗?
答案 0 :(得分:9)
我认为如果你想使用PHPUnit的模拟构建器,那么创建一个包含所有方法的数组,删除你需要的那个,并将它传递给setMethods正是你需要做的。
我个人发现在很多情况下有一个ReflectionClass的子类,我可以在需要时添加方法。
class MyReflectionClass extends ReflectionClass
{
public function getAllMethodNamesExcept(array $excluded)
{
$methods = array_diff(
get_class_methods($this->name),
$excluded
);
return $methods;
}
}
您还可以使用支持您要执行的操作的其他模拟框架。例如,Phake有一个thenCallParent方法。我最近开始使用Phake因为我需要能够捕获传递给方法的参数。这是有据可查的,值得一试。
答案 1 :(得分:1)
我需要模拟受保护的方法,这是Zach的实现ReflectionClass的答案的略微修改版本:
$class = new ReflectionClass(\Foo\Bar::class);
// Get just the method names:
$methods = array_map(function($m){return $m->name;}, $class->getMethods());
$methodsToMock = array_diff(
$methods,
array("baz", "qux") // don't mock these.
);
$mockObj = $this->getMockBuilder("\Foo\Bar")
->setConstructorArgs(array($this->foo))
->setMethods($methodsToMock)
->getMock();
$mockObj->baz(); // run as normal.
$mockObj->qux(); // run as normal.
$mockObj->foobar(); // mocked.