【问题标题】:How to mock chain of methods with PHPUnit test如何使用 PHPUnit 测试模拟方法链
【发布时间】:2017-02-27 10:20:55
【问题描述】:

我正在尝试模拟一个方法链(嵌套)以返回所需的值,这是代码:

public function __construct($db)
{
$this->db = $db;
}

public function getResults()
{
return $this->db->getFinder()->find($this->DBTable);
}

我试过这个模拟,但它不起作用:

$dbMock = $this->createMock(DB::class);
        $dbMock = $dbMock
            ->expects(self::any())
            ->method('getFinder')
            ->method('find')
            ->with('questions')
            ->will($this->returnValue('7'));

任何解决方案如何解决此类问题?

谢谢。

【问题讨论】:

    标签: php unit-testing mocking phpunit


    【解决方案1】:

    现在使用Mocking Demeter Chains And Fluent Interfaces 更简单

    简单

    $dbMock = $dbMock
            ->expects(self::any())
            ->method('getFinder->find')
            ->with('questions')
            ->will($this->returnValue('7'));
    

    另一个来自嘲弄文档的例子

    $object->foo()->bar()->zebra()->alpha()->selfDestruct();
    

    你想让selfDestruct返回10

    $mock = \Mockery::mock('CaptainsConsole');
    $mock->shouldReceive('foo->bar->zebra->alpha->selfDestruct')->andReturn(10);
    

    【讨论】:

      【解决方案2】:

      虽然@BVengerov 他的回答肯定会奏效,但我建议改为更改设计。我认为链接模拟不是可行的方法,它会损害可读性,更重要的是会损害测试的简单性。

      我建议你让Finder 类成为你的类的成员。因此,您现在只需模拟出Finder。

      class MyClass {
      
          private $finder;
      
          public function __construct(Finder $finder) {
              $this->finder = $finder;
          }
      
          public function getResults() {
              return $this->finder->find($this->DBTable);
          }
      }
      

      此更改使单元测试此函数(和类!)简单。

      “但是我需要班级其他地方的$db 变量!”嗯,首先,这可能表明您当前类中的一个类垂死要被提取。保持课程小而简单。

      但是,作为一种快速而简单的解决方案,请考虑添加一个 setFinder() 设置器,仅供测试使用。

      【讨论】:

      • 感谢@Pete,但每次将 RedBeanPHP 的查找器和工具箱注入每个类以便能够正确测试它会非常痛苦,请查看最后一次提交:github.com/AsemKhatib/MyPoll-OOP-PHP-Poll-system/commit/… 所以我需要注入他们都在工厂的每个瞬间:github.com/AsemKhatib/MyPoll-OOP-PHP-Poll-system/blob/testing/… 而不是仅仅注入整个 db 类。我真的很困惑。
      • 您的Factory 是“服务定位器”模式的实现(例如,所有类都接收定位器并从中检索他们实际需要的服务)。最简单的解决方案可能是模拟定位器(工厂)。但是,这要求您确切知道被测类需要哪些依赖项(这就是为什么有些人认为这是anti-pattern)。您是否考虑过使用 PHP-DI 等依赖注入框架?
      • 请注意,这样的框架并不是绝对必要的,但它解决了没有框架的纯依赖注入的“非常痛苦”的方面。
      • 感谢@Pete,我会在移除工厂后尝试实现 DI,并让您知道发生了什么,非常感谢。
      • @Pete 我不明白。但是现在测试链式数学的问题被转移到了另一个类。现在他需要在 Finder 类中对这些链进行单元测试。我错了吗?
      【解决方案3】:

      链由一个接一个地调用的对象组成。因此,您需要实现一个模拟链。只需以返回模拟对象的方式模拟方法即可。

      这样的事情应该可以工作:

      $finderMock = $this->createMock(Finder::class);
      $finderMock = $finderMock
          ->expects(self::any)
          ->method('find')
          ->with('questions')
          ->will($this->returnValue('7'));
      
      $dbMock = $this->createMock(DB::class);
      $dbMock = $dbMock
          ->expects(self::any())
          ->method('getFinder')
          ->will($this->returnValue($finderMock));
      

      阅读更多关于模拟链接in this cool blog post。

      不过,我并没有真正看到测试链的意义。 IMO 最好将测试限制为一次测试 1 个模块(功能)或 2 个模块(交互)。

      【讨论】:

      • 谢谢,但是当我尝试这个时:pastie.org/private/cpxfphhjbl2y4ih7iudjra 它不起作用,我收到了这个错误:
      • PHP 致命错误:在第 56 行的 /var/www/public/mypoll/tests/unit/classes/PaginationTest.php 中调用未定义的方法 PHPUnit_Framework_MockObject_Builder_InvocationMocker::getFinder()
      • @ebncana 太奇怪了...你能在Pagination 的构造函数中调试一下,看看你得到的是什么对象吗?
      • 我无法从 CLI 运行调试,但这是分页类:pastie.org/private/lcd33hz5et32iu0amzlsqw
      • @ebncana 它不起作用的事实困扰着我......如果你尝试the following code会发生什么?
      猜你喜欢
      • 1970-01-01
      • 2019-03-13
      • 2013-04-05
      • 1970-01-01
      • 1970-01-01
      • 2018-05-26
      • 1970-01-01
      • 1970-01-01
      • 2012-05-13
      相关资源
      最近更新 更多