在CakePHP中的模型方法中模拟一个方法

时间:2016-07-22 16:58:01

标签: php unit-testing cakephp phpunit

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

让我们调用模型Item,然后尝试测试其getStatus方法。

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

这样的事情:

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"的逻辑有点复杂,这就是我想为它编写一些测试的原因。

但是,我不完全确定如何覆盖find内的Item::getStatus来电。

通常,当我想模拟一个模型的函数时,我使用$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方法的内部调用?

2 个答案:

答案 0 :(得分:2)

我知道这必须是其他人面临的问题,事实证明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...
    ];
}

答案 1 :(得分:0)

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

您可以创建一个扩展项目和覆盖查找的TestItem,以便它不会执行数据库调用。

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