【问题标题】:PHPUnit mocking - fail immediately when method called x timesPHPUnit mocking - 当方法调用 x 次时立即失败
【发布时间】:2011-09-10 12:11:53
【问题描述】:

使用 PHPUnit,我正在使用 ->at() 测试一系列方法调用,如下所示:

$mock->expects($this->at(0))->method('execute')->will($this->returnValue('foo'));
$mock->expects($this->at(1))->method('execute')->will($this->returnValue('bar'));
$mock->expects($this->at(2))->method('execute')->will($this->returnValue('baz'));

如何设置模拟,以便在上述场景中,if execute() 被调用四次或更多次,它会立即失败?我试过这个:

$mock->expects($this->at(3))->method('execute')->will($this->throwException(new Exception('Called too many times.')));

但是如果 execute() 被 not 调用四次,这也会失败。它需要立即失败,否则被测系统会产生自己的错误,导致产生的错误信息不清楚。

【问题讨论】:

    标签: php unit-testing mocking phpunit


    【解决方案1】:

    我最终设法找到了解决方案。我使用了$this->returnCallback() 的组合并传递了 PHPUnit 匹配器来跟踪调用计数。然后你可以抛出一个 PHPUnit 异常,这样你也能得到很好的输出:

    $matcher = $this->any();
    $mock
        ->expects($matcher)
        ->method('execute')
        ->will($this->returnCallback(function() use($matcher) {
            switch ($matcher->getInvocationCount())
            {
                case 0: return 'foo';
                case 1: return 'bar';
                case 2: return 'baz';
            }
    
            throw new PHPUnit_Framework_ExpectationFailedException('Called too many times.');
        }))
    ;
    

    【讨论】:

    • 次要修复:调用编号从 1 开始,而不是 0
    【解决方案2】:

    对于像这样的特殊情况,我通常会使用如下内容:

    public function myMockCallback() {
         ++$this -> _myCounter;
         if( $this -> _myCounter > 3 ) {
              // THROW EXCEPTION OR TRIGGER ERROR
         }
         ... THEN YOUR CASE STATEMENT OR IF/ELSE WITH YOUR CHOICE OF RETURN VALUES
    } 
    
    ... INSIDE TEST FUNCTION ....
    
    $mockObject ->expects($this->any())
                ->method('myMethod')
                ->will($this->returnCallback( array ($this, 'myMockCallback' )));
    

    【讨论】:

    • 我总是很难在文档中找到类似的东西(滚动...滚动),但我也在这里找到了一个示例:phpunit.de/manual/current/en/… 请参见示例 11.5:存根方法调用从回调中返回一个值希望这会有所帮助!
    • 谢谢!这与我最终所做的非常相似。我已经发布了解决方案,但是我没有足够的声誉来回答我自己的问题:(我做的不同的一件事是获取$this->any()(一个'匹配器')返回的对象并将它传递给通过use() 回调,因为它实际上跟踪它被调用了多少次,你可以通过调用一个方法来获得它(我认为是getInvocationCount())。
    • 整洁,我得去看看。谢谢!
    【解决方案3】:

    使用data providers 怎么样?

    class MyTest extends PHPUnit.... { 
        /** 
         * @var const how much till throwing exception 
        */
        const MAX_EXECUTE_TILL_EXCEPTION = 3; 
    
        public function setUp(){} 
    
        public function tearDown(){} 
    
        /**
         *  @dataProvider MyExecuteProvider  
        */
        pulbic function testMyExecuteReturnFalse($data){
            $mock = //setup your mock here
    
            //if using "$ret" doesn't work you cant just call another private helper that will decide if you need to 
            //  return value or throwing exception 
            if (self::MAX_EXECUTE_TILL_EXCEPTION == $data){ 
                $ret = $this->throwException(new Exception('Called too many times.'));
            } else {
                $ret = $this->returnValue('foo'); 
            } 
            $mock->expects($this->at($data))->method('execute')->will($ret);
        }
    
        public function MyExecuteProvider(){
                return array(
                0,1,2,3
            )
        }
    }
    

    这只是另一个想法,我认为 zerkms 也提出了非常好的想法

    【讨论】:

    • 我认为这行不通。我正在尝试测试一系列调用,而不是特定索引处的单个调用。感谢您的意见。
    【解决方案4】:

    您可以使用 @depends 注释将测试分成 2 个 dependent 方法。

    在这种情况下,您的第一个测试只测试有确切的 3 个方法执行,第二个 - 其他逻辑。

    【讨论】:

    • 有趣的答案。在这种情况下,我实际上是在尝试编写一个辅助方法,该方法将返回一个模拟,所有这些期望都基于表示为数组的序列动态设置。它必须非常简单,因为我会经常重复使用它,所以不幸的是,为我正在测试的每个序列编写两个测试有点太麻烦了。
    • @Ezzatron:是的。如果第一个(“帮助”)失败,则不会调用 iirc 第二个测试方法。好吧,如果这不适合你,那就别想了……
    猜你喜欢
    • 1970-01-01
    • 2012-01-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-08
    • 1970-01-01
    相关资源
    最近更新 更多