PHPUnit中的模拟 - 使用不同参数的相同方法的多个配置

Vác*_*tný 49 php phpunit mocking

是否可以以这种方式配置PHPUnit mock?

$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()));
Run Code Online (Sandbox Code Playgroud)

我使用PHPUnit 3.5.10,当我要求Matcher时它会失败,因为它需要"Logger"参数.这就像第二个期望是重写第一个,但是当我转储模拟时,一切看起来都不错.

edo*_*ian 61

遗憾的是,使用默认的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()));
Run Code Online (Sandbox Code Playgroud)

这样可以正常工作,但是你测试的比你应该更多(主要是先用matcher调用它,这是一个实现细节).

如果您对每个功能进行多次调用,这也将失败!


接受这两个参数并使用returnCallBack

这是更多的工作,但更好,因为你不依赖于调用的顺序:

工作范例:

<?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"; }
}
Run Code Online (Sandbox Code Playgroud)

这将输出:

/*
$ 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)
Run Code Online (Sandbox Code Playgroud)

$this->exactly(2)在匹配器中使用过来表明这也适用于计算调用.如果你不需要将它换成$this->any()遗嘱,当然是工作.

  • @MarijnHuizendveld"绑定一系列预期调用的闭包,删除它看到的所有人以及测试结束时的assertEmpty"将是第一个浮现在脑海中的东西. (2认同)
  • 请注意 -&gt;at($x) 包括对其他方法的函数调用,因此如果您先模拟另一个方法,无论它是相同的方法还是使用 -&gt;at($x) 本身,$x 都会从 1 开始的 0。 (2认同)

lee*_*eeb 29

从PHPUnit 3.6开始,$this->returnValueMap()可以使用它根据给定方法存根的给定参数返回不同的值.

  • 是的,您可以使用returnValueMap(),但要确保指定该方法所需的所有参数,否则它将无法正常工作.请参阅https://github.com/sebastianbergmann/phpunit-mock-objects/issues/89 (2认同)

Gor*_*don 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

应该传递给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)
Run Code Online (Sandbox Code Playgroud)

如您所见,PHPUnit运行了两个测试.每个dataProvider值一个.在每个测试中,它都为断言with()和断言做出了instanceOf,因此有四个断言.


cry*_*lus 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');
Run Code Online (Sandbox Code Playgroud)

这是PHPUnit 3.7.

如果您正在测试的方法实际上没有返回任何内容,并且您只需要测试使用正确的参数调用它,则应用相同的方法.对于这种情况,我还尝试使用$ this-> callback的回调函数作为with的参数,而不是遗嘱中的returnCallback.这失败了,因为内部phpunit 两次调用回调在验证参数匹配器回调的过程中.这意味着该方法在第二次调用时失败,该参数已从预期的arguments数组中删除.我不知道为什么phpunit调用它两次(似乎是不必要的浪费),我猜你可以通过在第二次调用时删除它来解决这个问题,但我没有足够的信心这是有意和一致的phpunit行为依靠那种情况发生.