【问题标题】:Mock a method within a model method in CakePHP在 CakePHP 的模型方法中模拟方法
【发布时间】:2016-07-22 16:58:00
【问题描述】:

我正在运行 CakePHP 2.8.X,并且正在尝试为模型函数编写单元测试。

我们将模型称为Item,我正在尝试测试它的getStatus 方法。

但是,该模型在 getStatus 方法中调用其 find

所以是这样的:

class Item extends Model
{
    public function getStatus($id) {
      // Calls our `$this->Item-find` method
      $item = $this->find('first', [
        'fields' => ['status'],
        'conditions' => ['Item.id' => $id]
      ]);

      $status = $item['status'];

      $new_status = null;

      // Some logic below sets `$new_status` based on `$status`
      // ...

      return $new_status;
    }
}

设置“$new_status”的逻辑有点复杂,所以我想为它写一些测试。

但是,我不完全确定如何在 Item::getStatus 中覆盖 find 调用。

通常当我想模拟模型的函数时,我使用$this->getMockmethod('find')->will($this->returnValue($val_here)),但我不想完全模拟我的Item,因为我想测试它的实际getStatus 函数。

也就是说,在我的测试函数中,我将调用:

// This doesn't work since `$this->Item->getStatus` calls out to
// `$this->Item->find`, which my test suite doesn't know how to compute.
$returned_status = $this->Item->getStatus($id);
$this->assertEquals($expected_status, $returned_status);

那么我如何在我的测试中与我的真实Item 模型沟通,让它覆盖其对其find 方法的内部调用?

【问题讨论】:

    标签: php unit-testing cakephp phpunit


    【解决方案1】:

    我知道这一定是其他人面临的问题,事实证明 PHPUnit 有一个非常简单的方法来解决这个问题!

    This tutorial 基本上给了我答案。

    我确实需要创建一个模拟,但是通过只传入 'find' 作为我想模拟的方法,PHPUnit 有助于将所有其他方法单独留在我的模型中,并且 不会 覆盖他们。

    上述教程的相关部分是:

    将一组方法名称传递给您的 getMock 第二个参数会生成一个模拟对象,其中您已识别出方法

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

    而您没有识别的方法

    • 都是模拟,
    • 在调用时运行包含在方法中的实际代码强调我的),
    • 不允许您覆盖返回值

    意思是,我可以采用那个模拟模型,并直接从它调用我的getStatus 方法。该方法将运行它的真实代码,当它到达find() 时,它只会返回我传递给$this->returnValue 的任何内容。

    我使用dataProvider 传递我希望find 方法返回的内容,以及在我的assertEquals 调用中测试的结果。

    所以我的测试函数看起来像:

    /**
     * @dataProvider provideGetItemStatus
     */
    public function testGetItemStatus($item, $status_to_test) {
        // Only mock the `find` method, leave all other methods as is
        $item_model = $this->getMock('Item', ['find']);
    
        // Override our `find` method (should only be called once)
        $item_model
            ->expects($this->once())
            ->method('find')
            ->will($this->returnValue($item));
    
        // Call `getStatus` from our mocked model.
        // 
        // The key part here is I am only mocking the `find` method,
        // so when I call `$item_model->getStatus` it is actually
        // going to run the real `getStatus` code. The only method
        // that will return an overridden value is `find`.
        // 
        // NOTE: the param for `getStatus` doesn't matter since I only use it in my `find` call, which I'm overriding
        $result = $item_model->getStatus('dummy_id');
    
        $this->assertEquals($status_to_test, $result);
    }
    
    public function provideGetItemStatus() {
        return [
            [
                // $item
                ['Item' => ['id' = 1, 'status' => 1, /* etc. */]],
    
                // status_to_test
                1
            ],
    
            // etc...
        ];
    }
    

    【讨论】:

    • 您可以接受自己的答案,以便其他人知道您已经解决了问题。干得好,这将对其他人有用。
    【解决方案2】:

    模拟查找的一种方法是使用测试特定的子类。

    您可以创建一个扩展 item 并覆盖 find 的 TestItem,这样它就不会执行 db 调用。

    另一种方法是封装 new_status 逻辑并独立于模型对其进行单元测试

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-11-19
      • 1970-01-01
      • 2020-09-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多