#php #laravel #unit-testing #phpunit #mockery
#php #laravel #модульное тестирование #phpunit #издевательство
Вопрос:
Я большой поклонник Cache::remember
функциональности Laravel, и я использую ее в своих классах обслуживания, подобных этому:
/**
* 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(‘aaaa45-bbbb-cccc-ddddssswwwwdw_summary_cache’, ’10’, объект (закрытие)). Либо метод был неожиданным, либо его аргументы не соответствовали ожидаемому списку аргументов для этого метода
Объекты: ( array ( ‘Closure’ => array ( ‘class’ => ‘Closure’, ‘properties’ => array ( ), ), ))
Есть идеи о том, как это правильно протестировать?
Ответ №1:
Хорошо, я, кажется, слишком усложнил это; поэтому я разбил его и исправил немного по-другому, вот так.
Мой сервисный код теперь выглядит так:
/**
* SummaryService
*/
public function getSummaryData(string $userId)
{
$summaryCacheKey = $userId . '_summary_cache';
$summaryCacheLifespanMinutes = config('summary_cache_lifespan_minutes');
return Cache::remember($summaryCacheKey, $summaryCacheLifespanMinutes, function () use ($userId) {
return $this->loadLiveSummaryData($userId);
});
}
public function loadLiveSummaryData(string $userId)
{
$summaryResult = [
'userExists' => false,
'data' => [],
];
$user = $this->userRepository->findById($userId);
if ($user) {
$summaryResult = [
'userExists' => true,
'data' => $this->summaryRepository->getSummaryByUserId($user->id),
];
}
return $summaryResult;
}
и теперь мне просто нужно подтвердить с помощью моего модульного теста, что:
- Моя служба может загружать кэшированную версию и сопоставлять параметры вызова
- и я могу загружать текущие данные (где я могу издеваться над репозиториями)
Который выглядит примерно так:
/**
* @test
*/
public function i_can_load_live_summary_data_for_existing_user()
{
// given
$userId = 'aaaa45-bbbb-cccc-ddddssswwwdw';
$expectedResult = [
'userExists' => true,
'data' => [ ... ],
];
$user = new User();
$user->id = $userId;
$this->mockedUserRepository
->shouldReceive('findById')
->once()
->andReturn($user);
$this->mockedSummaryRepository
->shouldReceive('getSummaryByUserId')
->once()
->with($user->id)
->andReturn([ ... ]);
// when
$result = $this->summaryService->loadLiveSummaryData($userId);
// then
$this->assertSame($expectedResult, $result);
}
/**
* @test
*/
public function i_expect_cache_to_be_called_when_loading_summary_data_for_specific_user()
{
// given
$userId = 'aaaa45-bbbb-cccc-ddddssswwwdw';
$expectedResult = [
'userExists' => true,
'data' => [ ... ],
];
$summaryCacheKey = $userId . '_summary_cache';
$summaryCacheLifespanMinutes = 10;
Cache::shouldReceive('remember')
->once()
->with($summaryCacheKey, $summaryCacheLifespanMinutes, Mockery::on(function($value) {
return is_callable($value);
}))
->andReturn($expectedResult);
// when
$result = $this->summaryService->getSummaryData($userId);
// then
$this->assertSame($expectedResult, $result);
}
Дайте мне знать, был ли лучший или «правильный» способ сделать это.
Ответ №2:
Была похожая ситуация, когда я хотел протестировать оба пути, когда данные возвращаются через кэш и когда выполняется функция обратного вызова.
Ключевым моментом для меня было не использовать какой-либо метод макета фасада (например Cache::shouldReceive('remember')
), а затем будет запущен код обратного вызова.
Теперь кажется довольно очевидным: (