1. Overview
1.概述
In addition to implementations, we can use Spring’s declarative caching mechanism to annotate interfaces. For instance, we can declare caching on a Spring Data repository.
除了实现之外,我们还可以使用Spring的声明式缓存机制来注释接口。例如,我们可以在Spring Data存储库上声明缓存。
In this tutorial, we’re going to show how to test such a scenario.
在本教程中,我们将展示如何测试这样一个场景。
2. Getting Started
2.入门
First, let’s create a simple model:
首先,让我们创建一个简单的模型。
@Entity
public class Book {
@Id
private UUID id;
private String title;
}
And then, let’s add a repository interface that has a @Cacheable method:
然后,让我们添加一个拥有@Cacheable方法的存储库接口。
public interface BookRepository extends CrudRepository<Book, UUID> {
@Cacheable(value = "books", unless = "#a0=='Foundation'")
Optional<Book> findFirstByTitle(String title);
}
The unless condition here is not mandatory. It will just help us test some cache-miss scenarios in a moment.
这里的unless条件不是强制性的。它只是帮助我们在稍后测试一些缓存丢失的情况。
Also, note the SpEL expression “#a0” instead of the more readable “#title”. We do this because the proxy won’t keep the parameter names. So, we use the alternative #root.arg[0], p0 or a0 notation.
另外,注意SpEL表达式“#a0” ,而不是更易读的“#title” 。我们这样做是因为代理不会保留参数名称。所以,我们使用替代的#root.arg[0], p0 or a0符号。
3. Testing
3.测试
The goal of our tests is to make sure the caching mechanism works. Therefore, we don’t intend to cover the Spring Data repository implementation or the persistence aspects.
我们测试的目标是确保缓存机制的工作。因此,我们不打算涵盖Spring Data存储库的实现或持久性方面。
3.1. Spring Boot
3.1.Spring启动
Let’s start with a simple Spring Boot test.
让我们从一个简单的Spring Boot测试开始。
First, we’ll set up our test dependencies, add some test data, and create a simple utility method to check whether a book is in the cache or not:
首先,我们将设置我们的测试依赖,添加一些测试数据,并创建一个简单的实用方法来检查一本书是否在缓存中。
@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<Book> getCachedBook(String title) {
return ofNullable(cacheManager.getCache("books")).map(c -> c.get(title, Book.class));
}
Now, let’s make sure that after requesting a book, it gets placed in the cache:
现在,让我们确保请求一本书后,它被放在缓存中。
@Test
void givenBookThatShouldBeCached_whenFindByTitle_thenResultShouldBePutInCache() {
Optional<Book> dune = repository.findFirstByTitle("Dune");
assertEquals(dune, getCachedBook("Dune"));
}
And also, that some books are not placed in the cache:
还有,有些书没有被放在缓存中。
@Test
void givenBookThatShouldNotBeCached_whenFindByTitle_thenResultShouldNotBePutInCache() {
repository.findFirstByTitle("Foundation");
assertEquals(empty(), getCachedBook("Foundation"));
}
In this test, we make use of the Spring-provided CacheManager and check that after each repository.findFirstByTitle operation, the CacheManager contains (or does not contain) books according to the @Cacheable rules.
在这个测试中,我们利用Spring提供的CacheManager,并根据@Cacheable规则检查每次repository.findFirstByTitle操作后,CacheManager包含(或不包含)书籍。
3.2. Plain Spring
3.2.普通Spring
Let’s now continue with a Spring integration test. And for a change, this time let’s mock our interface. Then we’ll verify interactions with it in different test cases.
现在让我们继续进行Spring集成测试。为了改变现状,这次我们来模拟我们的接口。然后我们将在不同的测试案例中验证与它的交互。
We’ll start by creating a @Configuration that provides the mock implementation for our BookRepository:
我们将首先创建一个@Configuration,为我们的BookRepository提供mock实现。
@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");
}
}
Before moving on to setting up our mock’s behavior, there are two aspects worth mentioning about successfully using Mockito in this context:
在继续设置我们的mock的行为之前,有两个方面值得一提,那就是在这种情况下成功使用Mockito。
- BookRepository is a proxy around our mock. So, in order to use Mockito validations, we retrieve the actual mock via AopTestUtils.getTargetObject
- We make sure to reset(mock) in between tests because CachingTestConfig loads only once
@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!"));
}
Now, we can add our test methods. We’ll start by making sure that after a book is placed in the cache, there are no more interactions with the repository implementation when later trying to retrieve that book:
现在,我们可以添加我们的测试方法。我们首先要确保当一本书被放入缓存后,在以后试图检索该书时,不会再与存储库发生任何交互实现。
@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);
}
And we also want to check that for non-cached books, we invoke the repository every time:
我们还想检查一下对于非缓存的书籍,我们每次都会调用版本库。
@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. Summary
4.摘要
To sum it up, we used Spring, Mockito, and Spring Boot to implement a series of integration tests that make sure the caching mechanism applied to our interface works properly.
总而言之,我们使用Spring、Mockito和Spring Boot来实现一系列的集成测试,确保应用于我们接口的缓存机制正常工作。
Note that we could also combine the approaches above. For example, nothing stops us from using mocks with Spring Boot or from performing checks on the CacheManager in the plain Spring test.
请注意,我们也可以结合上述方法。例如,没有什么能阻止我们在Spring Boot中使用mocks或在普通的Spring测试中对CacheManager进行检查。
The complete code is available over on GitHub.
完整的代码可在GitHub上找到。