如何在 Spring Data 存储库上测试 Spring 的声明性缓存支持?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/24221569/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 06:47:18  来源:igfitidea点击:

How to test Spring's declarative caching support on Spring Data repositories?

springtestingspring-dataspring-data-jpaspring-cache

提问by balteo

I have developed a Spring Data repository, MemberRepositoryinterface, that extends org.springframework.data.jpa.repository.JpaRepository. MemberRepositoryhas a method:

我开发了一个 Spring Data 存储库、MemberRepository接口,它扩展了org.springframework.data.jpa.repository.JpaRepository. MemberRepository有一个方法:

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

The result is cached by Spring cache abstraction (backed by a ConcurrentMapCache).

结果由 Spring 缓存抽象(由 a 支持ConcurrentMapCache)缓存。

The issue I have is that I want to write an integration test (against hsqldb) that asserts that the result is retrieved from db the first timeand from cache the second time.

我的问题是,我想要写一个集成测试(针对HSQLDB)断言结果被从数据库第一次检索,并从缓存中的第二次

I initially thought of mocking the jpa infrastructure (entity manager, etc.) and somehow assert that the entity manager is not called the second time but it seems too hard/cumbersome (see https://stackoverflow.com/a/23442457/536299).

我最初想到嘲笑 jpa 基础设施(实体管理器等)并以某种方式断言实体管理器不会第二次被调用,但它似乎太难/太麻烦了(见https://stackoverflow.com/a/23442457/536299)。

Can someone then please provide advice as to how to test the caching behavior of a Spring Data Repository method annotated with @Cacheable?

那么有人可以提供有关如何测试带有注释的 Spring Data Repository 方法的缓存行为的建议@Cacheable吗?

回答by Oliver Drotbohm

If you want to test a technical aspect like caching, don't use a database at all. It's important to understand what you'd like to test here. You want to make sure the method invocation is avoided for the invocation with the very same arguments. The repository fronting a database is a completely orthogonal aspect to this topic.

如果您想测试缓存等技术方面,请根本不要使用数据库。了解您想在这里测试什么很重要。您想确保避免使用完全相同的参数调用方法调用。面向数据库的存储库与该主题完全正交。

Here's what I'd recommend:

这是我的建议:

  1. Set up an integration test that configures declarative caching (or imports the necessary bit's and pieces from your production configuration.
  2. Configure a mock instance of your repository.
  3. Write a test case to set up the expected behavior of the mock, invoke the methods and verify the output accordingly.
  1. 设置一个配置声明性缓存的集成测试(或从您的生产配置中导入必要的零碎部分。
  2. 配置存储库的模拟实例。
  3. 编写一个测试用例来设置模拟的预期行为,调用方法并相应地验证输出。

Sample

样本

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

As you can see, we do a bit of over-testing here:

如您所见,我们在这里进行了一些过度测试:

  1. The most relevant check, I think is that the second call returns the first object. That's what the caching is all about. The first two calls with the same key return the same object, whereas the third call with a different key results in the second actual invocation on the repository.
  2. We strengthen the test case by checking that the cache actually has a value for the first key. One could even extend that to check for the actual value. On the other hand, I also think it's fine to avoid doing that as you tend to test more of the internals of the mechanism rather than the application level behavior.
  1. 最相关的检查,我认为是第二次调用返回第一个对象。这就是缓存的全部意义所在。使用相同键的前两次调用返回相同的对象,而使用不同键的第三次调用导致对存储库的第二次实际调用。
  2. 我们通过检查缓存实际上具有第一个键的值来加强测试用例。人们甚至可以扩展它以检查实际值。另一方面,我也认为避免这样做是可以的,因为您倾向于测试更多机制的内部而不是应用程序级别的行为。

Key take-aways

关键要点

  1. You don't need any infrastructure to be in place to test container behavior.
  2. Setting a test case up is easy and straight forward.
  3. Well-designed components let you write simple test cases and require less integration leg work for testing.
  1. 您不需要任何基础设施来测试容器行为。
  2. 设置测试用例简单而直接。
  3. 精心设计的组件让您可以编写简单的测试用例,并且需要较少的集成测试工作。

回答by Mustafa

I tried testing the cache behavior in my app using Oliver's example. In my case my cache is set at the service layer and I want to verify that my repo is being called the right number of times. I'm using spock mocks instead of mockito. I spent some time trying to figure out why my tests are failing, until I realized that tests running first are populating the cache and effecting the other tests. After clearing the cache for every test they started behaving as expected.

我尝试使用 Oliver 的示例在我的应用程序中测试缓存行为。在我的情况下,我的缓存设置在服务层,我想验证我的 repo 被调用的次数是否正确。我正在使用 spock mocks 而不是 mockito。我花了一些时间试图找出我的测试失败的原因,直到我意识到首先运行的测试正在填充缓存并影响其他测试。在为每个测试清除缓存后,它们开始按预期运行。

Here's what I ended up with:

这是我的结果:

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