Рубрики
Без рубрики

Тестирование @Cacheable в хранилищах данных Spring

Узнайте, как изолировать слой кэширования в тестах данных Spring.

Автор оригинала: baeldung.

1. Обзор

В дополнение к реализациям мы можем использовать декларативный механизм кэширования Spring для аннотирования интерфейсов . Например, мы можем объявить кэширование в хранилище данных Spring.

В этом уроке мы покажем, как протестировать такой сценарий.

2. Начало работы

Во-первых, давайте создадим простую модель:

@Entity
public class Book {

    @Id
    private UUID id;
    private String title;

}

А затем давайте добавим интерфейс репозитория, который имеет метод @Cacheable :

public interface BookRepository extends CrudRepository {

    @Cacheable(value = "books", unless = "#a0=='Foundation'")
    Optional findFirstByTitle(String title);

}

Условие если только здесь не является обязательным. Это просто поможет нам проверить некоторые сценарии пропуска кэша в одно мгновение.

Кроме того, обратите внимание на выражение SpEL “#a0” вместо более читаемого “#title” . Мы делаем это, потому что прокси не будет сохранять имена параметров. Итак, мы используем альтернативную нотацию #root.arg[0], p0 или a0 .

3. Тестирование

Цель наших тестов состоит в том, чтобы убедиться, что механизм кэширования работает. Поэтому мы не намерены освещать реализацию хранилища данных Spring или аспекты сохранения.

3.1. Пружинный ботинок

Давайте начнем с простого теста весенней загрузки.

Во-первых, мы настроим наши тестовые зависимости, добавим некоторые тестовые данные и создадим простой служебный метод, чтобы проверить, находится ли книга в кэше или нет:

@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = CacheApplication.class)
public class BookRepositoryIntegrationTest {

    @Autowired
    CacheManager cacheManager;

    @Autowired
    BookRepository repository;

    @BeforeEach
    void setUp() {
        repository.save(new Book(UUID.randomUUID(), "Dune"));
        repository.save(new Book(UUID.randomUUID(), "Foundation"));
    }

    private Optional getCachedBook(String title) {
        return ofNullable(cacheManager.getCache("books")).map(c -> c.get(title, Book.class));
    }

Теперь давайте убедимся , что после запроса книги она будет помещена в кэш :

    @Test
    void givenBookThatShouldBeCached_whenFindByTitle_thenResultShouldBePutInCache() {
        Optional dune = repository.findFirstByTitle("Dune");

        assertEquals(dune, getCachedBook("Dune"));
    }

А также, что некоторые книги не помещаются в кэш :

    @Test
    void givenBookThatShouldNotBeCached_whenFindByTitle_thenResultShouldNotBePutInCache() {
        repository.findFirstByTitle("Foundation");

        assertEquals(empty(), getCachedBook("Foundation"));
    }

В этом тесте мы используем предоставленный Spring Менеджер кэша и проверяем , что после каждой операции repository.findFirstByTitle файл CacheManager содержит (или не содержит) книги в соответствии с правилами @Cacheable .

3.2. Простая пружина

Теперь давайте продолжим весенний интеграционный тест. И для разнообразия, на этот раз давайте поиздеваемся над нашим интерфейсом. Затем мы проверим взаимодействие с ним в разных тестовых случаях.

Мы начнем с создания @Конфигурации , которая обеспечивает макет реализацию для нашего Хранилища книг :

@ContextConfiguration
@ExtendWith(SpringExtension.class)
public class BookRepositoryCachingIntegrationTest {

    private static final Book DUNE = new Book(UUID.randomUUID(), "Dune");
    private static final Book FOUNDATION = new Book(UUID.randomUUID(), "Foundation");

    private BookRepository mock;

    @Autowired
    private BookRepository bookRepository;

    @EnableCaching
    @Configuration
    public static class CachingTestConfig {

        @Bean
        public BookRepository bookRepositoryMockImplementation() {
            return mock(BookRepository.class);
        }

        @Bean
        public CacheManager cacheManager() {
            return new ConcurrentMapCacheManager("books");
        }

    }

Прежде чем перейти к настройке поведения нашего макета, стоит упомянуть два аспекта успешного использования Mockito в этом контексте:

  • Хранилище книг является прокси-сервером вокруг нашего макета. Итак, чтобы использовать Mockito проверки, мы получаем фактический макет через Тест Aop Utils.getTargetObject
  • Мы обязательно сбрасываем(макет) между тестами, потому что Кэширование тестовой конфигурации загружается только один раз
    @BeforeEach
    void setUp() {
        mock = AopTestUtils.getTargetObject(bookRepository);

        reset(mock);

        when(mock.findFirstByTitle(eq("Foundation")))
                .thenReturn(of(FOUNDATION));

        when(mock.findFirstByTitle(eq("Dune")))
                .thenReturn(of(DUNE))
                .thenThrow(new RuntimeException("Book should be cached!"));
    }

Теперь мы можем добавить наши методы тестирования. Мы начнем с того, что убедимся, что после того, как книга будет помещена в кэш, больше не будет взаимодействия с репозиторием реализации при последующей попытке получить эту книгу:

    @Test
    void givenCachedBook_whenFindByTitle_thenRepositoryShouldNotBeHit() {
        assertEquals(of(DUNE), bookRepository.findFirstByTitle("Dune"));
        verify(mock).findFirstByTitle("Dune");

        assertEquals(of(DUNE), bookRepository.findFirstByTitle("Dune"));
        assertEquals(of(DUNE), bookRepository.findFirstByTitle("Dune"));

        verifyNoMoreInteractions(mock);
    }

И мы также хотим проверить , что для некешированных книг мы каждый раз вызываем репозиторий :

    @Test
    void givenNotCachedBook_whenFindByTitle_thenRepositoryShouldBeHit() {
        assertEquals(of(FOUNDATION), bookRepository.findFirstByTitle("Foundation"));
        assertEquals(of(FOUNDATION), bookRepository.findFirstByTitle("Foundation"));
        assertEquals(of(FOUNDATION), bookRepository.findFirstByTitle("Foundation"));

        verify(mock, times(3)).findFirstByTitle("Foundation");
    }

4. Резюме

Подводя итог, мы использовали Spring, Mockito и Spring Boot для реализации серии интеграционных тестов, которые проверяют правильность работы механизма кэширования, применяемого к нашему интерфейсу.

Обратите внимание, что мы также могли бы объединить вышеприведенные подходы. Например, ничто не мешает нам использовать mocks с Spring Boot или выполнять проверки в Cache Manager в простом весеннем тесте.

Полный код доступен на GitHub .