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 OptionalgetCachedBook(String title) { return ofNullable(cacheManager.getCache("books")).map(c -> c.get(title, Book.class)); }
Теперь давайте убедимся , что после запроса книги она будет помещена в кэш :
@Test void givenBookThatShouldBeCached_whenFindByTitle_thenResultShouldBePutInCache() { Optionaldune = 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 .