phpunit mock方法使用不同的参数进行多次调用

Ale*_*kin 107 php phpunit mocking

有没有办法为不同的输入参数定义不同的模拟期望?例如,我有一个名为DB的数据库层类.此类具有名为"Query(string $ query)"的方法,该方法在输入时采用SQL查询字符串.我可以为此类(DB)创建模拟,并为依赖于输入查询字符串的不同Query方法调用设置不同的返回值吗?

hir*_*ari 158

at()如果你可以避免使用它是不理想的,因为正如他们的文档声称的那样

at()匹配器的$ index参数指的是在给定模拟对象的所有方法调用中从零开始的索引.使用此匹配器时要小心,因为它可能导致脆弱的测试,这些测试与特定的实现细节过于紧密相关.

从4.1开始你就可以使用withConsecutiveeg.

$mock->expects($this->exactly(2))
     ->method('set')
     ->withConsecutive(
         [$this->equalTo('foo'), $this->greaterThan(0)],
         [$this->equalTo('bar'), $this->greaterThan(0)]
       );
Run Code Online (Sandbox Code Playgroud)

如果你想让它在连续的通话中返回:

  $mock->method('set')
         ->withConsecutive([$argA1, $argA2], [$argB1], [$argC1, $argC2])
         ->willReturnOnConsecutiveCalls($retValueA, $retValueB, $retValueC);
Run Code Online (Sandbox Code Playgroud)

  • 截至2016年的最佳答案.比接受的答案更好. (18认同)
  • PHPUnit 10 弃用 at() 和 withConsecutive()! (4认同)

edo*_*ian 123

PHPUnit Mocking库(默认情况下)确定期望是否仅基于传递给expects参数的匹配器和传递给的约束匹配method.因此,两个expect仅在传递给的参数中不同的调用with将失败,因为两者将匹配,但只有一个将验证为具有预期的行为.请参阅实际工作示例后的复制案例.


对于您的问题,您需要使用->at()->will($this->returnCallback(如中所述another question on the subject.

例:

<?php

class DB {
    public function Query($sSql) {
        return "";
    }
}

class fooTest extends PHPUnit_Framework_TestCase {


    public function testMock() {

        $mock = $this->getMock('DB', array('Query'));

        $mock
            ->expects($this->exactly(2))
            ->method('Query')
            ->with($this->logicalOr(
                 $this->equalTo('select * from roles'),
                 $this->equalTo('select * from users')
             ))
            ->will($this->returnCallback(array($this, 'myCallback')));

        var_dump($mock->Query("select * from users"));
        var_dump($mock->Query("select * from roles"));
    }

    public function myCallback($foo) {
        return "Called back: $foo";
    }
}
Run Code Online (Sandbox Code Playgroud)

转载:

phpunit foo.php
PHPUnit 3.5.13 by Sebastian Bergmann.

string(32) "Called back: select * from users"
string(32) "Called back: select * from roles"
.

Time: 0 seconds, Memory: 4.25Mb

OK (1 test, 1 assertion)
Run Code Online (Sandbox Code Playgroud)

重现为什么两个 - > with()调用不工作:

<?php

class DB {
    public function Query($sSql) {
        return "";
    }
}

class fooTest extends PHPUnit_Framework_TestCase {


    public function testMock() {

        $mock = $this->getMock('DB', array('Query'));
        $mock
            ->expects($this->once())
            ->method('Query')
            ->with($this->equalTo('select * from users'))
            ->will($this->returnValue(array('fred', 'wilma', 'barney')));

        $mock
            ->expects($this->once())
            ->method('Query')
            ->with($this->equalTo('select * from roles'))
            ->will($this->returnValue(array('admin', 'user')));

        var_dump($mock->Query("select * from users"));
        var_dump($mock->Query("select * from roles"));
    }

}
Run Code Online (Sandbox Code Playgroud)

结果是

 phpunit foo.php
PHPUnit 3.5.13 by Sebastian Bergmann.

F

Time: 0 seconds, Memory: 4.25Mb

There was 1 failure:

1) fooTest::testMock
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-select * from roles
+select * from users

/home/.../foo.php:27

FAILURES!
Tests: 1, Assertions: 0, Failures: 1
Run Code Online (Sandbox Code Playgroud)

  • 谢谢你的帮助!你的答案彻底解决了我的问题.PS当我必须使用如此大的解决方案来进行简单的架构时,有时TDD开发对我来说似乎很可怕:) (7认同)
  • 我想知道没有人提到,使用“-&gt;logicalOr()”你不能保证(在这种情况下)两个参数都被调用。所以这并不能真正解决问题。 (3认同)

Rad*_*zea 12

根据我的发现,解决此问题的最佳方法是使用PHPUnit的值映射功能.

PHPUnit文档中的示例:

class SomeClass {
    public function doSomething() {}   
}

class StubTest extends \PHPUnit_Framework_TestCase {
    public function testReturnValueMapStub() {

        $mock = $this->getMock('SomeClass');

        // Create a map of arguments to return values.
        $map = array(
          array('a', 'b', 'd'),
          array('e', 'f', 'h')
        );  

        // Configure the mock.
        $mock->expects($this->any())
             ->method('doSomething')
             ->will($this->returnValueMap($map));

        // $mock->doSomething() returns different values depending on
        // the provided arguments.
        $this->assertEquals('d', $stub->doSomething('a', 'b'));
        $this->assertEquals('h', $stub->doSomething('e', 'f'));
    }
}
Run Code Online (Sandbox Code Playgroud)

这个测试通过.如你看到的:

  • 当使用参数"a"和"b"调用函数时,返回"d"
  • 当使用参数"e"和"f"调用函数时,返回"h"

据我所知,这个功能是在PHPUnit 3.6中引入的,因此它"足够",可以安全地用于几乎任何开发或登台环境以及任何持续集成工具.


joe*_*erx 5

似乎Mockery(https://github.com/padraic/mockery)支持这一点。就我而言,我想检查是否在数据库上创建了2个索引:

模拟,作品:

use Mockery as m;

//...

$coll = m::mock(MongoCollection::class);
$db = m::mock(MongoDB::class);

$db->shouldReceive('selectCollection')->withAnyArgs()->times(1)->andReturn($coll);
$coll->shouldReceive('createIndex')->times(1)->with(['foo' => true]);
$coll->shouldReceive('createIndex')->times(1)->with(['bar' => true], ['unique' => true]);

new MyCollection($db);
Run Code Online (Sandbox Code Playgroud)

PHPUnit,这将失败:

$coll = $this->getMockBuilder(MongoCollection::class)->disableOriginalConstructor()->getMock();
$db  = $this->getMockBuilder(MongoDB::class)->disableOriginalConstructor()->getMock();

$db->expects($this->once())->method('selectCollection')->with($this->anything())->willReturn($coll);
$coll->expects($this->atLeastOnce())->method('createIndex')->with(['foo' => true]);
$coll->expects($this->atLeastOnce())->method('createIndex')->with(['bar' => true], ['unique' => true]);

new MyCollection($db);
Run Code Online (Sandbox Code Playgroud)

Mockery还具有更好的语法恕我直言。它似乎比PHPUnits的内置模拟功能要慢一些,但比YMMV慢。