PHPUnit:模拟 __get() 导致“__get() 必须恰好采用 1 个参数 ...”

Posted

技术标签:

【中文标题】PHPUnit:模拟 __get() 导致“__get() 必须恰好采用 1 个参数 ...”【英文标题】:PHPUnit: Mocking __get() results in "__get() must take exactly 1 argument ..." 【发布时间】:2013-02-13 04:27:36 【问题描述】:

我在模拟重载的 __get($index) 方法时遇到了问题。 要模拟的类和使用它的被测系统的代码如下:

<?php
class ToBeMocked

    protected $vars = array();

    public function __get($index)
    
        if (isset($this->vars[$index])) 
            return $this->vars[$index];
         else 
            return NULL;
        
    


class SUTclass

    protected $mocky;

    public function __construct(ToBeMocked $mocky)
    
        $this->mocky = $mocky;
    

    public function getSnack()
    
        return $this->mocky->snack;
    

测试如下:

<?php    
class GetSnackTest extends PHPUnit_Framework_TestCase

    protected $stub;
    protected $sut;

    public function setUp()
    
       $mock = $this->getMockBuilder('ToBeMocked')
                     ->setMethods(array('__get')
                     ->getMock();

       $sut = new SUTclass($mock);
    

    /**
     * @test
     */
    public function shouldReturnSnickers()
    
        $this->mock->expects($this->once())
                   ->method('__get')
                   ->will($this->returnValue('snickers');

        $this->assertEquals('snickers', $this->sut->getSnack());
    

实际代码稍微复杂一点,虽然不多,但在其父类中有“getSnacks()”。但是这个例子就足够了。

问题是我在使用 PHPUnit 执行测试时收到以下错误:

Fatal error: Method Mock_ToBeMocked_12345672f::__get() must take exactly 1 argument in /usr/share/php/PHPUnit/Framework/MockObject/Generator.php(231)

当我调试时,我什至无法达到测试方法。似乎在设置模拟对象时会中断。

有什么想法吗?

【问题讨论】:

好的,我进一步测试了一点。问题似乎真的出在模拟类的构造上。如果我用 ->setMethods(array('blabla')) 创建它,他就会使用测试方法。如果我使用 ->setMethods(array('__get')) 他会中断。 你是对的 - 问题与 PHP 模拟对象如何生成模拟有关 - 下面的答案都没有解决这个问题。我在 PHPUnit 4.0 中遇到了这个问题 - 你使用的是什么版本? 我尝试使用 PHPUnit 版本的不同组合重新创建它,它是从可用的最新版本(撰写本文时为 4.8.2)回到 PHPUnit 3.5 的 Mock Object 依赖项,但无法获得它使用我的环境以这种方式打破 - Ubuntu 12.04.5 LTS 上的 PHP 5.5。除非原始发帖人可以通过记住他们使用的版本组合来扩展这个问题,否则我建议关闭这个问题。 Phiu,3 年后通过谷歌回到这个问题。 :D 我认为早期是 PHP 5.3 和 PHPUnit 3.6.x 或 3.7.x。好久没报错了。现在我使用 PHP 5.5 / PHPUnit 4.8.26 再次获得它。我认为这是我们所有单元测试以某种方式相互交叉输入的问题。因为回到 3.7.38 我得到了不同的错误。我会尽力将问题本地化一些。也许这只是过去美好时代的一些恶臭测试。 :D 【参考方案1】:

__get() 需要一个参数,因此您需要为模拟提供一个:

/**
 * @test
 */
public function shouldReturnSnickers()

    $this->mock->expects($this->once())
               ->method('__get')
               ->with($this->equalTo('snack'))
               ->will($this->returnValue('snickers'));

    $this->assertEquals('snickers', $this->sut->getSnack());

with() 方法设置 PHPUnit 中模拟方法的参数。您可以在Test Doubles 部分找到更多详细信息。

【讨论】:

包含with() 调用是我希望解决的问题,但它不适合我(PHPUnit 4.4)。你在给出这个答案之前test了吗?只是想知道您是否只是在重复文档,还是知道这样做可以工作? 我想我当时使用的是 3.7。这(或类似的东西)有效,但我在写这篇文章时找不到我所引用的内容。但是没有,我测试了这段代码。 干杯。我一定是在做一些愚蠢的事情(我有点 PHP 新手) FWIW,在您发表评论后,我无法让它在 4.4 上运行,所以这可能不适用于新版本,或者我复制了错误的内容(看起来我在至少)。那么谁知道呢。我在我从中提取的项目中停止使用__get 我在使用 with() 时也遇到了问题,只是无法正常工作。我能够使用这个$planMock-&gt;method('__get')-&gt;willReturnCallback(function ($propertyName) // switch $propertyName and return value ); 成功地为每个魔术方法返回正确的响应【参考方案2】:

它在 cmets 中有点隐藏,但 @dfmuir 的回答让我走上了正轨。如果您使用回调,则可以直接模拟 __get 方法。

$mock
    ->method('__get')
    ->willReturnCallback(function ($propertyName) 
        switch($propertyName) 
            case 'id':
                return 123123123123;
            case 'name':
                return 'Bob';
            case 'email':
                return 'bob@bob.com';
        
    
);

$this->assertEquals('bob@bob.com', $mock->email);

【讨论】:

【参考方案3】:

查看模拟的魔术方法__get。可能您从另一个未正确模拟的对象中再调用一个 __get 方法。

【讨论】:

【参考方案4】:

您在 GetSnackTest 类的 setUp 方法中所做的操作不正确。 如果您希望执行__get 方法的代码(这将是您测试的重点> 我想),您必须更改在setup 方法中调用setMethods 的方式。 Here's the complete explanation,但这里是相关部分:

传递一个包含方法名的数组

你已经确定的方法:

都是存根, 默认全部返回null, 很容易被覆盖

因此,您需要通过传递 null 或传递包含 一些 方法(您真正想要存根的方法)的数组来调用 setMethods ),但 不是- __get(因为您实际上希望执行该方法的代码)。

shouldReturnSnickers 方法中,您只想调用$this-&gt;assertEquals('snickers', $this-&gt;sut-&gt;getSnack());,而不需要前面带有expect 部分的行。 这将确保您的 __get 方法的代码被实际执行和测试。

【讨论】:

【参考方案5】:

withAnyParameters() 方法可以帮助你,这是正确的:

$this->mock -> expects($this -> once())  
    -> method('__get') -> withAnyParameters()
    -> will($this -> returnValue('snikers'));

【讨论】:

以上是关于PHPUnit:模拟 __get() 导致“__get() 必须恰好采用 1 个参数 ...”的主要内容,如果未能解决你的问题,请参考以下文章

使用 PHPUnit 模拟 PDO 对象

我可以用 PHPUnit 模拟接口实现吗?

我可以使用PHPUnit模拟接口实现吗?

PHPUnit 模拟和类型提示方法

PHPUnit 在预期异常后不继续测试

用于 PHPunit 的 Laravel 5.4 模型模拟