在 CakePHP 的模型方法中模拟一个方法
Mock a method within a model method in CakePHP
我是 运行 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->getMock
和 method('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
方法的内部调用?
模拟查找的一种方法是使用特定于测试的子类。
您可以创建一个扩展 item 并覆盖 find 的 TestItem,这样它就不会执行数据库调用。
另一种方法是封装 new_status 逻辑并独立于模型对其进行单元测试
我知道这一定是其他人面临的问题,事实证明 PHPUnit 有一种非常简单的方法来解决这个问题!
This tutorial 基本上给了我答案。
我确实需要创建一个模拟,但是通过只传入 'find'
作为我想要模拟的方法,PHPUnit 有助于将所有其他方法单独留在我的模型中并且 确实不覆盖它们。
上述教程的相关部分是:
Passing an array of method names to your getMock
second argument produces a mock object where the methods you have identified
- Are all stubs,
- All return null by default,
- Are easily overridable
Whereas methods you did not identify
- Are all mocks,
- Run the actual code contained within the method when called (emphasis mine),
- Do not allow you to override the return value
意思是,我可以使用那个模拟模型,并直接从它 调用我的 getStatus
方法 。该方法将 运行 它的真实代码,当它到达 find()
时,它将只是 return 我传递给 $this->returnValue
.
的任何内容
我使用 dataProvider
将我想要的查找方法传递给 return,以及在我的 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...
];
}
我是 运行 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->getMock
和 method('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
方法的内部调用?
模拟查找的一种方法是使用特定于测试的子类。
您可以创建一个扩展 item 并覆盖 find 的 TestItem,这样它就不会执行数据库调用。
另一种方法是封装 new_status 逻辑并独立于模型对其进行单元测试
我知道这一定是其他人面临的问题,事实证明 PHPUnit 有一种非常简单的方法来解决这个问题!
This tutorial 基本上给了我答案。
我确实需要创建一个模拟,但是通过只传入 'find'
作为我想要模拟的方法,PHPUnit 有助于将所有其他方法单独留在我的模型中并且 确实不覆盖它们。
上述教程的相关部分是:
Passing an array of method names to your
getMock
second argument produces a mock object where the methods you have identified
- Are all stubs,
- All return null by default,
- Are easily overridable
Whereas methods you did not identify
- Are all mocks,
- Run the actual code contained within the method when called (emphasis mine),
- Do not allow you to override the return value
意思是,我可以使用那个模拟模型,并直接从它 调用我的 getStatus
方法 。该方法将 运行 它的真实代码,当它到达 find()
时,它将只是 return 我传递给 $this->returnValue
.
我使用 dataProvider
将我想要的查找方法传递给 return,以及在我的 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...
];
}