PHPUnit “模拟方法不存在。”当使用 $mock->expects($this->at(...))

Posted

技术标签:

【中文标题】PHPUnit “模拟方法不存在。”当使用 $mock->expects($this->at(...))【英文标题】:PHPUnit "Mocked method does not exist." when using $mock->expects($this->at(...)) 【发布时间】:2011-03-23 00:05:24 【问题描述】:

我遇到了一个关于 phpUnit 模拟对象的奇怪问题。我有一个应该被调用两次的方法,所以我使用了“at”匹配器。这在第一次调用该方法时有效,但由于某种原因,第二次调用它时,我得到“模拟方法不存在。”。我以前使用过“at”匹配器,但从未遇到过。

我的代码如下所示:

class MyTest extends PHPUnit_Framework_TestCase

    ...

    public function testThis()
    
        $mock = $this->getMock('MyClass', array('exists', 'another_method', '...'));
        $mock->expects($this->at(0))
             ->method('exists')
             ->with($this->equalTo('foo'))
             ->will($this->returnValue(true));

        $mock->expects($this->at(1))
             ->method('exists')
             ->with($this->equalTo('bar'))
             ->will($this->returnValue(false));
    

    ...

当我运行测试时,我得到:

Expectation failed for method name is equal to <string:exists> when invoked at sequence index 1.
Mocked method does not exist.

如果我删除第二个匹配器,我不会收到错误消息。

以前有人遇到过这种情况吗?

谢谢!

【问题讨论】:

【参考方案1】:

问题最终在于我如何理解“at”匹配器的工作原理。另外,我的示例在我的单元测试中并没有逐字记录。我认为“at”匹配器计数器是在每个查询的基础上工作的,它实际上是在每个对象实例的基础上工作的。

例子:

class MyClass 

    public function exists($foo) 
        return false;
    

    public function find($foo) 
        return $foo;
    

不正确:

class MyTest extends PHPUnit_Framework_TestCase


    public function testThis()
    
        $mock = $this->getMock('MyClass');
        $mock->expects($this->at(0))
             ->method('exists')
             ->with($this->equalTo('foo'))
             ->will($this->returnValue(true));

        $mock->expects($this->at(0))
             ->method('find')
             ->with($this->equalTo('foo'))
             ->will($this->returnValue('foo'));

        $mock->expects($this->at(1))
             ->method('exists')
             ->with($this->equalTo('bar'))
             ->will($this->returnValue(false));

        $this->assertTrue($mock->exists("foo"));
        $this->assertEquals('foo', $mock->find('foo'));
        $this->assertFalse($mock->exists("bar"));
    


正确:

class MyTest extends PHPUnit_Framework_TestCase


    public function testThis()
    
        $mock = $this->getMock('MyClass');
        $mock->expects($this->at(0))
             ->method('exists')
             ->with($this->equalTo('foo'))
             ->will($this->returnValue(true));

        $mock->expects($this->at(1))
             ->method('find')
             ->with($this->equalTo('foo'))
             ->will($this->returnValue('foo'));

        $mock->expects($this->at(2))
             ->method('exists')
             ->with($this->equalTo('bar'))
             ->will($this->returnValue(false));

        $this->assertTrue($mock->exists("foo"));
        $this->assertEquals('foo', $mock->find('foo'));
        $this->assertFalse($mock->exists("bar"));
    


【讨论】:

是的,但我认为这是 PHPUnit 中的一个错误。文档说:返回一个匹配器,当它被评估的方法在给定的 $index 处被调用时匹配。 同意,另外,如果 at() 索引基于每个方法递增,那么监视方法调用会更容易和更有用。 看起来任何对期望的错误使用都会导致“模拟方法不存在”消息。很高兴知道。 这使得模拟多个方法几乎无法使用 今天它仍然这样工作,现在我用这篇文章修复了我的测试,我把帽子砸在地板上【参考方案2】:

仅供参考,不确定它是否相关,但我遇到了同样的事情,但不是$this-&gt;at() 方法,对我来说是$this-&gt;never() 方法。

这引发了错误

$mock->expects($this->never())
    ->method('exists')
    ->with('arg');

这修复了错误

$mock->expects($this->never())
    ->method('exists');  

在使用$this-&gt;exactly(0) 方法时,它做了同样的事情。

希望这对某人有所帮助。

【讨论】:

谢谢!这真的很有帮助,我遇到了同样的问题,我被卡住了(直到我读到你的评论)。 谢谢,这也解决了我的问题。但我真的不知道它为什么起作用。你能解释一下这件事吗? @SilviuG,由于永远不应该调用该方法,因此对 with 参数的期望永远不会发生。因此,由于该方法的配置期望没有发生但被配置为发生(因为 with),错误被触发。希望这是有道理的。我同意这个错误的消息有点奇怪。 谢谢!有完全相同的问题。【参考方案3】:

尝试将$this-&gt;at(1) 更改为$this-&gt;at(2)

【讨论】:

为我工作。我打电话给:foo 一次,bar foo 之后的方法 3 次。从 1 个固定问题开始 bar 期望。【参考方案4】:

这是 PHPUnit 错误消息的不幸措辞。

仔细检查您的通话顺序,例如@rr 的回答提及。

就我而言,据我所知,我应该分别使用at(0)at(1),但直到我使用at(2)at(3) 才起作用。 (我在 CakePHP 中使用会话模拟。)

检查顺序的最佳方法是“进入”被调用的方法并检查传递的内容。你可以这样做:

$cakePost = $this->getMock('CakePost');
$cakePost->expects($this->once())
->method('post')
->with(
    // Add a line like this for each arg passed
    $this->callback(function($arg) 
        debug("Here's what was passed: $arg");
    )
);

【讨论】:

天哪,真烦人!谢谢你,由于这个问题,我真的在为单元测试而苦苦挣扎。【参考方案5】:

据我从演示代码中可以看出,它应该工作。我制作了一个工作示例,以防您运行的是较旧的 PHPUnit 版本并想检查它是否也适用于您。

如果这没有帮助,也许您可​​以提供更多(最好是可执行的)代码? :)

<?php

class MyTest extends PHPUnit_Framework_TestCase


    public function testThis()
    
        $mock = $this->getMock('MyClass');
        $mock->expects($this->at(0))
             ->method('exists')
             ->with($this->equalTo('foo'))
             ->will($this->returnValue(true));

        $mock->expects($this->at(1))
             ->method('exists')
             ->with($this->equalTo('bar'))
             ->will($this->returnValue(false));

        $this->assertTrue($mock->exists("foo"));
        $this->assertFalse($mock->exists("bar"));
    



class MyClass 

    public function exists($foo) 
        return false;
    

打印

phpunit MyTest.php
PHPUnit 3.4.15 by Sebastian Bergmann.

.

Time: 0 seconds, Memory: 4.25Mb

OK (1 test, 3 assertions)

【讨论】:

【参考方案6】:

你确定你的测试中包含了 MyClass 吗?在模拟类/接口而不包含它时,我遇到了一些未定义的方法错误。

【讨论】:

是的,第一个匹配器工作正常,就是头部刮刀【参考方案7】:

可能不是在提出问题时,但是今天documentation 明确指定了应该如何使用 at 并且我引用了

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

【讨论】:

以上是关于PHPUnit “模拟方法不存在。”当使用 $mock->expects($this->at(...))的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 PEAR 正确安装 PHPUnit?

使用 wordpress 的 phpunit 上的 RecursiveDirectoryIterator 错误

使用 phpunit 进行 Laravel 测试

PHPUnit --coverage-html无效

仅当访问 Laravel 5 中的根页面( visit('/') )时,PHPUnit 测试才会失败

Laravel PhpUnit 依赖注入