PHP Laravel 使用模拟测试
PHP Laravel tests using mocking
我正在为我的存储库编写测试,我有一个 page() 方法,它接受一个 PaginationQuery 对象,该对象由特定模型的限制、方向和光标组成,returns json paginator 对象,我正在为模拟而苦苦挣扎,这是我的代码:
我的存储库代码:
public function page(PaginationQuery $pagination)
{
$paginator = new Paginator($this->model);
return $paginator->paginate($pagination->limit(), $pagination->direction(), $pagination->cursor());
}
我的测试:
public function test_paging_with_mocking()
{
// seed articles
$seededArticles = [
new Article(['title' => 'Article 1']),
new Article(['title' => 'Article 2']),
new Article(['title' => 'Article 3']),
new Article(['title' => 'Article 4']),
new Article(['title' => 'Article 5']),
new Article(['title' => 'Article 6']),
];
foreach ($seededArticles as $seededArticle) {
$seededArticle->save();
}
$limit = PaginationQuery::defaultLimit();
$direction = PaginationQuery::defaultDirection();
$cursor = PaginationQuery::defaultCursor();
$pagination = new PaginationQuery($limit, $direction, $cursor);
$mPaginator = M::mock(Paginator::class);
$mPaginator->shouldReceive('paginate')->with($limit, $direction, $cursor)->andReturn([]);
$receivedResult = $this->repo->page($pagination);
}
我需要在这里完成几件事:
在模拟分页器的同时测试 page() 方法,因为我在这里不关心输出,我只需要断言限制、方向和光标值被正确传递给paginate() 方法。
我还需要在 page() 方法中注入分页器 class,因为模拟 class 的 new
实例会导致模拟此错误class
在这行测试中 $mPaginator->shouldReceive('paginate')->with($limit, $direction, $cursor)->andReturn([]);
我需要添加 once() 但是这样做时出现以下错误:
应该叫
恰好 1 次但调用 0 次
我认为这里的问题是,在 page
方法中,您使用以下方法创建新对象:
$paginator = new Paginator($this->model);
而不是以某种方式将其传递到存储库。
这导致永远不会使用分页器的模拟,因为创建了新对象。
您可以快速验证这一点,如果您使用 Paginator
return 的 paginate
方法,例如 testtesttest
并在测试结束时添加:
$this->assertEquals([], $receivedResult);
您将看到收到的结果将是 testtesttest
而不是 []
尽管您使用 andReturn([])
设置它,因为您没有将模拟对象传递到存储库中。
使测试通过的最简单解决方案是将您的 page
方法更改为:
public function page(PaginationQuery $pagination, Paginator $paginator)
{
return $paginator->paginate($pagination->limit(), $pagination->direction(), $pagination->cursor());
}
现在整个测试这样结束:
$mPaginator->shouldReceive('paginate')->once()->with($limit, $direction, $cursor)->andReturn([]);
$receivedResult = $this->repo->page($pagination, $mPaginator);
$this->assertEquals([], $receivedResult);
将通过,因为您将模拟对象传递到存储库,现在如您所见,即使在您的 Paginator
模型中您 return [=28],您也会得到 []
作为结果=]
编辑
在评论中您询问了如何注入 Paginator 对象,所以有可能的解决方案。
重写您的存储库以使用如下代码:
public function page(PaginationQuery $pagination)
{
$paginator = $this->getPaginator();
return $paginator->paginate($pagination->limit(),
$pagination->direction(), $pagination->cursor());
}
protected function getPaginator()
{
return new Paginator($this->model);
}
所以你现在正在创建额外的方法来创建分页器实例。
现在让我们回到您的测试。
您在测试构造函数的某处创建了存储库实例。假设您是这样做的:
$this->repo = new \App\Repositories\ArticleRepository();
现在,我们想把它改成这样的部分模拟:
$this->repo = M::mock("\App\Repositories\ArticleRepository[getPaginator]")->shouldAllowMockingProtectedMethods();
所以我们告诉我们将为 getPaginator
方法创建模拟并且我们使用 shouldAllowMockingProtectedMethods
因为我们不想模拟受保护的方法(getPaginator
是受保护的方法)并且它不能默认完成。
所以现在,最后一行测试应该如下所示:
$mPaginator->shouldReceive('paginate')->once()->with($limit, $direction, $cursor)->andReturn([]);
// here we mock getPaginator method to return our mock $mPaginator
$this->repo->shouldReceive('getPaginator')->once()->andReturn($mPaginator);
$receivedResult = $this->repo->page($pagination);
$this->assertEquals([], $receivedResult);
所以如您所见,我们现在实现了我们想要的,我们不必在 运行 page
方法时传递分页器对象。
我正在为我的存储库编写测试,我有一个 page() 方法,它接受一个 PaginationQuery 对象,该对象由特定模型的限制、方向和光标组成,returns json paginator 对象,我正在为模拟而苦苦挣扎,这是我的代码:
我的存储库代码:
public function page(PaginationQuery $pagination)
{
$paginator = new Paginator($this->model);
return $paginator->paginate($pagination->limit(), $pagination->direction(), $pagination->cursor());
}
我的测试:
public function test_paging_with_mocking()
{
// seed articles
$seededArticles = [
new Article(['title' => 'Article 1']),
new Article(['title' => 'Article 2']),
new Article(['title' => 'Article 3']),
new Article(['title' => 'Article 4']),
new Article(['title' => 'Article 5']),
new Article(['title' => 'Article 6']),
];
foreach ($seededArticles as $seededArticle) {
$seededArticle->save();
}
$limit = PaginationQuery::defaultLimit();
$direction = PaginationQuery::defaultDirection();
$cursor = PaginationQuery::defaultCursor();
$pagination = new PaginationQuery($limit, $direction, $cursor);
$mPaginator = M::mock(Paginator::class);
$mPaginator->shouldReceive('paginate')->with($limit, $direction, $cursor)->andReturn([]);
$receivedResult = $this->repo->page($pagination);
}
我需要在这里完成几件事:
在模拟分页器的同时测试 page() 方法,因为我在这里不关心输出,我只需要断言限制、方向和光标值被正确传递给paginate() 方法。
我还需要在 page() 方法中注入分页器 class,因为模拟 class 的
new
实例会导致模拟此错误class在这行测试中
$mPaginator->shouldReceive('paginate')->with($limit, $direction, $cursor)->andReturn([]);
我需要添加 once() 但是这样做时出现以下错误:应该叫 恰好 1 次但调用 0 次
我认为这里的问题是,在 page
方法中,您使用以下方法创建新对象:
$paginator = new Paginator($this->model);
而不是以某种方式将其传递到存储库。
这导致永远不会使用分页器的模拟,因为创建了新对象。
您可以快速验证这一点,如果您使用 Paginator
return 的 paginate
方法,例如 testtesttest
并在测试结束时添加:
$this->assertEquals([], $receivedResult);
您将看到收到的结果将是 testtesttest
而不是 []
尽管您使用 andReturn([])
设置它,因为您没有将模拟对象传递到存储库中。
使测试通过的最简单解决方案是将您的 page
方法更改为:
public function page(PaginationQuery $pagination, Paginator $paginator)
{
return $paginator->paginate($pagination->limit(), $pagination->direction(), $pagination->cursor());
}
现在整个测试这样结束:
$mPaginator->shouldReceive('paginate')->once()->with($limit, $direction, $cursor)->andReturn([]);
$receivedResult = $this->repo->page($pagination, $mPaginator);
$this->assertEquals([], $receivedResult);
将通过,因为您将模拟对象传递到存储库,现在如您所见,即使在您的 Paginator
模型中您 return [=28],您也会得到 []
作为结果=]
编辑
在评论中您询问了如何注入 Paginator 对象,所以有可能的解决方案。
重写您的存储库以使用如下代码:
public function page(PaginationQuery $pagination)
{
$paginator = $this->getPaginator();
return $paginator->paginate($pagination->limit(),
$pagination->direction(), $pagination->cursor());
}
protected function getPaginator()
{
return new Paginator($this->model);
}
所以你现在正在创建额外的方法来创建分页器实例。
现在让我们回到您的测试。
您在测试构造函数的某处创建了存储库实例。假设您是这样做的:
$this->repo = new \App\Repositories\ArticleRepository();
现在,我们想把它改成这样的部分模拟:
$this->repo = M::mock("\App\Repositories\ArticleRepository[getPaginator]")->shouldAllowMockingProtectedMethods();
所以我们告诉我们将为 getPaginator
方法创建模拟并且我们使用 shouldAllowMockingProtectedMethods
因为我们不想模拟受保护的方法(getPaginator
是受保护的方法)并且它不能默认完成。
所以现在,最后一行测试应该如下所示:
$mPaginator->shouldReceive('paginate')->once()->with($limit, $direction, $cursor)->andReturn([]);
// here we mock getPaginator method to return our mock $mPaginator
$this->repo->shouldReceive('getPaginator')->once()->andReturn($mPaginator);
$receivedResult = $this->repo->page($pagination);
$this->assertEquals([], $receivedResult);
所以如您所见,我们现在实现了我们想要的,我们不必在 运行 page
方法时传递分页器对象。