Как модульно протестировать все пути выполнения кода функции Cache::remember в Laravel?

#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;
}
 

и теперь мне просто нужно подтвердить с помощью моего модульного теста, что:

  1. Моя служба может загружать кэшированную версию и сопоставлять параметры вызова
  2. и я могу загружать текущие данные (где я могу издеваться над репозиториями)

Который выглядит примерно так:

 /**
 * @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') ), а затем будет запущен код обратного вызова.

Теперь кажется довольно очевидным: (