我是Laravel的Cache::remember
功能的忠实粉丝,我在我的服务类上使用它,如下所示:
/**
* SummaryService
*/
public function getSummaryData(string $userId)
{
$summaryCacheKey = $userId . '_summary_cache';
$summaryCacheLifespanMinutes = config('summary_cache_lifespan_minutes');
return Cache::remember($summaryCacheKey, $summaryCacheLifespanMinutes, function () use ($userId) {
$summaryResult = [
'userExists' => false,
'data' => [],
];
$user = $this->userRepository->findById($userId);
if ($user) {
$summaryResult = [
'userExists' => true,
'data' => $this->summaryRepository->getSummaryByUserId($user->id),
];
}
return $summaryResult;
});
}
这和预期的一样。如果数据存在该高速缓存中,则将其返回,如果不存在,则将其加载并缓存并返回。
现在,我正在尝试对我的SummaryService
(两个执行路径)进行单元测试。
通过缓存返回数据的第一部分很容易测试,它看起来像这样:
public function i_can_load_summary_data_via_cache()
{
// given
$userId = 'aaaa45-bbbb-cccc-ddddssswwwdw';
$expectedResult = [
'userExists' => true,
'data' => [ ... ],
];
$summaryCacheKey = $userId . '_summary_cache';
$summaryCacheLifespanMinutes = config('summary_cache_lifespan_minutes');
Cache::shouldReceive('remember')
->once()
->with($summaryCacheKey, $summaryCacheLifespanMinutes, Closure::class)
->andReturn($expectedResult);
// when
$result = $this->summaryService->getSummaryData($userId);
// then
$this->assertSame($expectedResult, $result);
}
然而,当我尝试测试数据不存在于缓存中的场景时,我必须加载它(通过模拟存储库),如下所示:
public function i_can_load_summary_data_via_database()
{
// given
$userId = 'aaaa45-bbbb-cccc-ddddssswwwdw';
$expectedResult = [
'userExists' => true,
'data' => [ ... ],
];
$user = new User();
$user->id = $userId;
$summaryCacheKey = $userId . '_summary_cache';
$summaryCacheLifespanMinutes = 0;
Cache::shouldReceive('remember')
->once()
->with($summaryCacheKey, $summaryCacheLifespanMinutes, \Mockery::on(function() use($user) {
$this->mockedUserRepository
->shouldReceive('findById')
->once()
->andReturn($user);
$this->mockedSummaryRepository
->shouldReceive('getSummaryByUserId')
->once()
->with($user->id)
->andReturn([ ... ]);
}))
->andReturn($expectedResult);
// when
$result = $this->summaryService->getSummaryData($userId);
// then
$this->assertSame($expectedResult, $result);
}
测试失败:
未找到Mockery_3_Illuminate_Cache_CacheManager::remember('aaa 45-bbb-cccc-ddddssswwwdw_summary_cache','10',object(Closure))的匹配处理程序。该方法是意外的,或者其参数不匹配此方法的预期参数列表
Object:(array('Closure' => array('class' => 'Closure','properties' => array(),),))
你知道怎么正确测试吗?
3条答案
按热度按时间hc8w905p1#
好吧,我好像把事情复杂化了;所以我把它分解了,并像这样稍微修改了一下。
我的服务代码现在看起来像这样:
现在,我只需要通过我的单元测试确认:
1.我的服务可以加载缓存版本并匹配调用参数
1.我可以加载实时数据(在那里我可以模拟存储库)
它看起来像这样:
让我知道是否有更好或“正确”的方法来做到这一点。
ppcbkaq52#
有一个类似的情况,我想测试两个路径,当数据通过该高速缓存返回时,当回调函数被执行时。
对我来说,关键是不要使用任何facade mock方法(例如:
Cache::shouldReceive('remember')
),然后回调代码将运行。现在看起来很明显:(
xjreopfe3#
我认为所提出的解决方案可以进一步改进,因为实际上要使其工作,您需要将代码移动到另一个方法中并使其公开。所以最后,你在类的公共契约中创建了一个新的方法,只是为了使它可测试。类的用户可以绕过该高速缓存机制,这在某些情况下可能会导致有害的后果。我们可以将此方法更改为protected或private,但这样我们就必须在测试中使用Reflection来更改方法的可见性以对其进行测试。
这里有一种方法可以做到这一点,而不改变测试的主题(我们正在测试的代码)。不需要提取新方法或使用反射。
andReturnUsing
是一个使用回调的方法,您可以使用该回调来生成该方法应该返回的内容。回调函数接收传递给remember
方法的原始参数。在本例中,我获取remember
方法的第三个参数,在本例中,该参数是在没有缓存命中时执行的回调。然后我执行该高速缓存回调并返回它返回的任何内容。注意:我通常更喜欢将
\Illuminate\Cache\Repository
接口传递给我的服务类的构造函数,而不是直接在服务类中使用\Cache
facade。我还没有测试过它,但我非常肯定你可以直接在facade上使用mocking方法,而不是像我一样使用Repository类。