tag mvc form spring testing spring-data spring-data-jpa spring-cache

mvc - ¿Cómo probar el soporte de almacenamiento en caché declarativo de Spring en los repositorios de Spring Data?



spring mvc tag select (2)

He desarrollado un repositorio de Spring Data, la interfaz MemberRepository , que extiende org.springframework.data.jpa.repository.JpaRepository . MemberRepository tiene un método:

@Cacheable(CacheConfiguration.DATABASE_CACHE_NAME) Member findByEmail(String email);

El resultado se almacena en caché mediante la abstracción de la memoria caché de Spring (respaldada por un ConcurrentMapCache ).

El problema que tengo es que quiero escribir una prueba de integración (contra hsqldb) que afirme que el resultado se recupera de la primera vez que se encuentra en la base de datos y de la caché la segunda vez .

Inicialmente pensé en burlarme de la infraestructura de jpa (administrador de entidades, etc.) y afirmar de alguna manera que no se llama al administrador de entidades la segunda vez, pero parece demasiado difícil / engorroso (consulte https://stackoverflow.com/a/23442457/536299 ).

¿Alguien puede proporcionar consejos sobre cómo probar el comportamiento de almacenamiento en caché de un método de Spring Data Repository anotado con @Cacheable ?


Intenté probar el comportamiento del caché en mi aplicación usando el ejemplo de Oliver. En mi caso, mi caché está configurado en la capa de servicio y quiero verificar que mi repositorio se esté llamando el número correcto de veces. Estoy usando burlas spock en lugar de mockito. Pasé un tiempo tratando de averiguar por qué fallan mis pruebas, hasta que me di cuenta de que las pruebas que se ejecutan primero llenan el caché y afectan a las otras pruebas. Después de borrar la memoria caché para cada prueba, comenzaron a comportarse como se esperaba.

Esto es lo que terminé con:

@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 } }


Si desea probar un aspecto técnico como el almacenamiento en caché, no utilice una base de datos. Es importante entender lo que te gustaría probar aquí. Desea asegurarse de que se evite la invocación del método para la invocación con los mismos argumentos. El repositorio al frente de una base de datos es un aspecto completamente ortogonal de este tema.

Esto es lo que yo recomendaría:

  1. Configure una prueba de integración que configure el almacenamiento en caché declarativo (o importe los bits y piezas necesarios de su configuración de producción.
  2. Configure una instancia simulada de su repositorio.
  3. Escriba un caso de prueba para configurar el comportamiento esperado del simulacro, invoque los métodos y verifique la salida en consecuencia.

Muestra

@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)); } }

Como puede ver, hacemos un poco de pruebas excesivas aquí:

  1. El control más relevante, creo, es que la segunda llamada devuelve el primer objeto. De eso se trata el caché. Las dos primeras llamadas con la misma clave devuelven el mismo objeto, mientras que la tercera llamada con una clave diferente da como resultado la segunda invocación real en el repositorio.
  2. Reforzamos el caso de prueba al verificar que la memoria caché tenga un valor para la primera clave. Incluso se podría extender eso para verificar el valor real. Por otro lado, también creo que está bien evitar hacerlo ya que tiende a probar más los mecanismos internos del mecanismo en lugar del comportamiento a nivel de la aplicación.

Clave para llevar

  1. No necesita ninguna infraestructura para probar el comportamiento del contenedor.
  2. Configurar un caso de prueba es sencillo y sencillo.
  3. Los componentes bien diseñados le permiten escribir casos de prueba simples y requieren menos trabajo de integración para realizar la prueba.