org.springframework.data.jpa.repository.JpaRepository
を拡張するSpring DataリポジトリMemberRepository
インターフェイスを開発しました。 MemberRepository
にはメソッドがあります:
@Cacheable(CacheConfiguration.DATABASE_CACHE_NAME)
Member findByEmail(String email);
結果は、Springキャッシュ抽象化によってキャッシュされます(ConcurrentMapCache
によって裏付けられます)。
私が持っている問題は、結果が最初にdbから取得されたことをアサートする(hsqldbに対する)統合テストを書きたいことですと2回目のキャッシュから。
私は当初、JPAインフラストラクチャ(エンティティマネージャなど)をモックすることを考え、何らかの理由でエンティティマネージャが2度目に呼び出されないことを主張しますが、あまりにも難しい/扱いにくいようです( https://stackoverflow.com/aを参照)/23442457/536299 )。
誰かが@Cacheable
で注釈されたSpring Data Repositoryメソッドのキャッシュ動作をテストする方法についてアドバイスを提供できますか?
キャッシングなどの技術的な側面をテストする場合は、データベースをまったく使用しないでください。ここでテストする内容を理解することが重要です。確実にしたいのですが、メソッドの呼び出しは、まったく同じ引数を使用した呼び出しでは回避されます。データベースの前にあるリポジトリは、このトピックと完全に直交する側面です。
私がお勧めするものは次のとおりです。
@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));
}
}
ご覧のとおり、ここでは少しオーバーテストを行っています。
Oliverの例を使用して、アプリのキャッシュ動作をテストしてみました。私の場合、キャッシュはサービス層に設定されており、リポジトリが適切な回数呼び出されていることを確認したいと思います。私はmockitoの代わりにspock mocksを使用しています。最初に実行されているテストがキャッシュに格納され、他のテストに影響を与えることに気付くまで、テストが失敗する理由を理解しようとして少し時間を費やしました。すべてのテストでキャッシュをクリアした後、期待どおりに動作し始めました。
ここに私が終わったものがあります:
@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
}
}