是否可以用这种方式配置PHPUnit?
$context = $this->getMockBuilder('Context')
->getMock();
$context->expects($this->any())
->method('offsetGet')
->with('Matcher')
->will($this->returnValue(new Matcher()));
$context->expects($this->any())
->method('offsetGet')
->with('Logger')
->will($this->returnValue(new Logger()));
我使用PHPUnit 3.5.10,当我要求Matcher时它会失败,因为它需要“Logger”参数。 这就像第二个期望是重写第一个,但是当我转储模拟时,一切看起来都不错。
答案 0 :(得分:60)
遗憾的是,默认的PHPUnit Mock API无法实现这一点。
我可以看到两个可以让你接近这样的选项:
$context = $this->getMockBuilder('Context')
->getMock();
$context->expects($this->at(0))
->method('offsetGet')
->with('Matcher')
->will($this->returnValue(new Matcher()));
$context->expects($this->at(1))
->method('offsetGet')
->with('Logger')
->will($this->returnValue(new Logger()));
这样可以正常工作,但是你测试的比你应该的多(主要是先用matcher调用它,这是一个实现细节)。
如果你对每个函数有多个调用,这也会失败!
这是更多的工作,但由于您不依赖于呼叫的顺序,因此效果更好:
<?php
class FooTest extends PHPUnit_Framework_TestCase {
public function testX() {
$context = $this->getMockBuilder('Context')
->getMock();
$context->expects($this->exactly(2))
->method('offsetGet')
->with($this->logicalOr(
$this->equalTo('Matcher'),
$this->equalTo('Logger')
))
->will($this->returnCallback(
function($param) {
var_dump(func_get_args());
// The first arg will be Matcher or Logger
// so something like "return new $param" should work here
}
));
$context->offsetGet("Matcher");
$context->offsetGet("Logger");
}
}
class Context {
public function offsetGet() { echo "org"; }
}
这将输出:
/*
$ phpunit footest.php
PHPUnit 3.5.11 by Sebastian Bergmann.
array(1) {
[0]=>
string(7) "Matcher"
}
array(1) {
[0]=>
string(6) "Logger"
}
.
Time: 0 seconds, Memory: 3.00Mb
OK (1 test, 1 assertion)
我在匹配器中使用$this->exactly(2)
来表明这也适用于计算调用。如果您不需要将其交换为$this->any()
,那么当然可以。
答案 1 :(得分:29)
从PHPUnit 3.6开始,有$this->returnValueMap()
可用于根据方法存根的给定参数返回不同的值。
答案 2 :(得分:7)
您可以通过回调实现此目的:
class MockTest extends PHPUnit_Framework_TestCase
{
/**
* @dataProvider provideExpectedInstance
*/
public function testMockReturnsInstance($expectedInstance)
{
$context = $this->getMock('Context');
$context->expects($this->any())
->method('offsetGet')
// Accept any of "Matcher" or "Logger" for first argument
->with($this->logicalOr(
$this->equalTo('Matcher'),
$this->equalTo('Logger')
))
// Return what was passed to offsetGet as a new instance
->will($this->returnCallback(
function($arg1) {
return new $arg1;
}
));
$this->assertInstanceOf(
$expectedInstance,
$context->offsetGet($expectedInstance)
);
}
public function provideExpectedInstance()
{
return array_chunk(array('Matcher', 'Logger'), 1);
}
}
应该传递给Context Mock的offsetGet
方法的任何“Logger”或“Matcher”参数:
F:\Work\code\gordon\sandbox>phpunit NewFileTest.php
PHPUnit 3.5.13 by Sebastian Bergmann.
..
Time: 0 seconds, Memory: 3.25Mb
OK (2 tests, 4 assertions)
如您所见,PHPUnit运行了两个测试。每个dataProvider值一个。在每个测试中,它都为with()
做出了断言,为instanceOf
做出了断言,因此有四个断言。
答案 3 :(得分:5)
继续@edorian的回答和评论(@MarijnHuizendveld)关于确保用Matcher和Logger调用方法,而不是简单地用Matcher或Logger调用两次,这是一个例子。
$expectedArguments = array('Matcher', 'Logger');
$context->expects($this->exactly(2))
->method('offsetGet')
->with($this->logicalOr(
$this->equalTo('Matcher'),
$this->equalTo('Logger')
))
->will($this->returnCallback(
function($param) use (&$expectedArguments){
if(($key = array_search($param, $expectedArguments)) !== false) {
// remove called argument from list
unset($expectedArguments[$key]);
}
// The first arg will be Matcher or Logger
// so something like "return new $param" should work here
}
));
// perform actions...
// check all arguments removed
$this->assertEquals(array(), $expectedArguments, 'Method offsetGet not called with all required arguments');
这是PHPUnit 3.7。
如果您正在测试的方法实际上没有返回任何内容,并且您只需要测试使用正确的参数调用它,则应用相同的方法。对于这种情况,我还尝试使用$ this-&gt;回调函数的回调函数作为with的参数,而不是遗嘱中的returnCallback。这失败了,因为内部phpunit在验证参数匹配器回调的过程中调用了回调两次。这意味着该方法在第二次调用时失败,该参数已从预期的arguments数组中删除。我不知道为什么phpunit调用它两次(似乎是不必要的浪费),我猜你可以通过在第二次调用时删除它来解决这个问题,但我没有足够的信心这是有意和一致的phpunit行为依靠那种情况发生。
答案 4 :(得分:2)
我偶然发现这个PHP扩展模拟对象:https://github.com/etsy/phpunit-extensions/wiki/Mock-Object
答案 5 :(得分:2)
我对主题的2美分:在使用at($ x)时要注意:这意味着预期的方法调用将是模拟对象上的($ x + 1)方法调用;这并不意味着将是预期方法的($ x + 1)调用。这让我浪费了一些时间,所以我希望它不会和你在一起。亲切的问候每个人。
答案 6 :(得分:0)
还有doublit库的一些解决方案:
解决方案1:使用Stubs::returnValueMap
/* Get a dummy double instance */
$double = Doublit::dummy_instance(Context::class);
/* Test the "offsetGet" method */
$double::_method('offsetGet')
// Test that the first argument is equal to "Matcher" or "Logger"
->args([Constraints::logicalOr('Matcher', 'Logger')])
// Return "new Matcher()" when first argument is "Matcher"
// Return "new Logger()" when first argument is "Logger"
->stub(Stubs::returnValueMap([['Matcher'], ['Logger']], [new Matcher(), new Logger()]));
解决方案2:使用回调
/* Get a dummy double instance */
$double = Doublit::dummy_instance(Context::class);
/* Test the "offsetGet" method */
$double::_method('offsetGet')
// Test that the first argument is equal to "Matcher" or "Logger"
->args([Constraints::logicalOr('Matcher', 'Logger')])
// Return "new Matcher()" when first argument $arg is "Matcher"
// Return "new Logger()" when first argument $arg is "Logger"
->stub(function($arg){
if($arg == 'Matcher'){
return new Matcher();
} else if($arg == 'Logger'){
return new Logger();
}
});