php-PHPUnit中的模拟-带有不同参数的同一方法的多个配置
是否可以通过这种方式配置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”参数。就像第二个期望重写了第一个期望一样,但是当我转储模拟时,一切看起来都还不错。
7个解决方案
61 votes
遗憾的是,默认的PHPUnit Mock API无法做到这一点。
我可以看到两个选择,可以使您接近以下情况:
使用-> at($ x)
$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()));
这可以正常工作,但是您要进行的测试超出了您应有的水平(主要是首先使用匹配器调用它,这是实现的详细信息)。
如果您对每个功能的调用不止一个,这也将失败!
接受两个参数并使用returnCallBack
这是更多的工作,但是效果更好,因为您不依赖于调用的顺序:
工作示例:
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()当然可以。
edorian answered 2020-02-07T10:34:17Z
30 votes
从PHPUnit 3.6开始,有$this->returnValueMap()可用于根据方法存根的给定参数返回不同的值。
leeb answered 2020-02-07T10:33:17Z
7 votes
您可以通过回调来实现:
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的273693388456172020320方法的任何“ 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()声明了一个,并为273693388456172020321声明了一个,因此有四个声明。
Gordon answered 2020-02-07T10:34:45Z
5 votes
接下来是@edorian的回答和有关确保使用Matcher和Logger调用该方法的评论(@MarijnHuizendveld),而不是简单地使用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-> callback的回调函数作为with的参数来执行此操作,而不是在will中使用returnCallback。 失败,因为内部phpunit在验证参数匹配器回调的过程中两次调用了该回调。 这意味着该方法失败,因为在第二次调用时该参数已从预期参数数组中删除。 我不知道为什么phpunit会两次调用它(似乎是不必要的浪费),我想您可以通过仅在第二次调用时将其删除来解决此问题,但是我对这是有目的的和一致的phpunit行为没有足够的信心 依靠这种发生。
crysallus answered 2020-02-07T10:35:16Z
3 votes
我对该主题的2美分:使用at($ x)时要注意:这意味着预期的方法调用将是模拟对象上的第($ x + 1)个方法调用; 这并不意味着将是预期方法的第($ x + 1)次调用。 这让我浪费了一些时间,所以我希望它不会与您在一起。 对大家致以诚挚的问候。
Alessandro Ronchi answered 2020-02-07T10:35:36Z
2 votes
我只是偶然发现了这个PHP扩展来模拟对象:[https://github.com/etsy/phpunit-extensions/wiki/Mock-Object]
powtac answered 2020-02-07T10:35:56Z
0 votes
这也是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();
}
});
gealex answered 2020-02-07T10:36:25Z