J'ai développé un référentiel Spring Data, MemberRepository
interface, qui étend org.springframework.data.jpa.repository.JpaRepository
. MemberRepository
a une méthode:
@Cacheable(CacheConfiguration.DATABASE_CACHE_NAME)
Member findByEmail(String email);
Le résultat est mis en cache par l'abstraction du cache Spring (soutenu par un ConcurrentMapCache
).
Le problème que j'ai est que je veux écrire un test d'intégration (contre hsqldb) qui affirme que le résultat est récupéré de db la première fois et à partir du cache pour la deuxième fois .
J'ai d'abord pensé à se moquer de l'infrastructure jpa (gestionnaire d'entité, etc.) et j'affirme en quelque sorte que le gestionnaire d'entité n'est pas appelé la deuxième fois, mais cela semble trop difficile/lourd (voir https://stackoverflow.com/a/23442457/536299 ).
Quelqu'un peut-il alors fournir des conseils sur la façon de tester le comportement de mise en cache d'une méthode Spring Data Repository annotée avec @Cacheable
?
Si vous souhaitez tester un aspect technique comme la mise en cache, n'utilisez pas du tout de base de données. Il est important de comprendre ce que vous souhaitez tester ici. Vous voulez vous assurer que l'invocation de la méthode est évitée pour l'invocation avec les mêmes arguments. Le référentiel devant une base de données est un aspect complètement orthogonal à ce sujet.
Voici ce que je recommanderais:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class CachingIntegrationTest {
// Your repository interface
interface MyRepo extends Repository<Object, Long> {
@Cacheable("sample")
Object findByEmail(String email);
}
@Configuration
@EnableCaching
static class Config {
// Simulating your caching configuration
@Bean
CacheManager cacheManager() {
return new ConcurrentMapCacheManager("sample");
}
// A repository mock instead of the real proxy
@Bean
MyRepo myRepo() {
return Mockito.mock(MyRepo.class);
}
}
@Autowired CacheManager manager;
@Autowired MyRepo repo;
@Test
public void methodInvocationShouldBeCached() {
Object first = new Object();
Object second = new Object();
// Set up the mock to return *different* objects for the first and second call
Mockito.when(repo.findByEmail(Mockito.any(String.class))).thenReturn(first, second);
// First invocation returns object returned by the method
Object result = repo.findByEmail("foo");
assertThat(result, is(first));
// Second invocation should return cached value, *not* second (as set up above)
result = repo.findByEmail("foo");
assertThat(result, is(first));
// Verify repository method was invoked once
Mockito.verify(repo, Mockito.times(1)).findByEmail("foo");
assertThat(manager.getCache("sample").get("foo"), is(notNullValue()));
// Third invocation with different key is triggers the second invocation of the repo method
result = repo.findByEmail("bar");
assertThat(result, is(second));
}
}
Comme vous pouvez le voir, nous faisons un peu de sur-test ici:
J'ai essayé de tester le comportement du cache dans mon application en utilisant l'exemple d'Oliver. Dans mon cas, mon cache est défini sur la couche de service et je veux vérifier que mon dépôt est appelé le bon nombre de fois. J'utilise des spock mocks au lieu de mockito. J'ai passé un certain temps à essayer de comprendre pourquoi mes tests échouent, jusqu'à ce que je réalise que les tests en cours d'exécution remplissent le cache et effectuent les autres tests. Après avoir vidé le cache pour chaque test, ils ont commencé à se comporter comme prévu.
Voici ce que j'ai fini avec:
@ContextConfiguration
class FooBarServiceCacheTest extends Specification {
@TestConfiguration
@EnableCaching
static class Config {
def mockFactory = new DetachedMockFactory()
def fooBarRepository = mockFactory.Mock(FooBarRepository)
@Bean
CacheManager cacheManager() {
new ConcurrentMapCacheManager(FOOBARS)
}
@Bean
FooBarRepository fooBarRepository() {
fooBarRepository
}
@Bean
FooBarService getFooBarService() {
new FooBarService(fooBarRepository)
}
}
@Autowired
@Subject
FooBarService fooBarService
@Autowired
FooBarRepository fooBarRepository
@Autowired
CacheManager cacheManager
def "setup"(){
// we want to start each test with an new cache
cacheManager.getCache(FOOBARS).clear()
}
def "should return cached foobars "() {
given:
final foobars = [new FooBar(), new FooBar()]
when:
fooBarService.getFooBars()
fooBarService.getFooBars()
final fooBars = fooBarService.getFooBars()
then:
1 * fooBarRepository.findAll() >> foobars
}
def "should return new foobars after clearing cache"() {
given:
final foobars = [new FooBar(), new FooBar()]
when:
fooBarService.getFooBars()
fooBarService.clearCache()
final fooBars = fooBarService.getFooBars()
then:
2 * fooBarRepository.findAll() >> foobars
}
}