48

MemberRepositoryを拡張する Spring Data リポジトリ、インターフェイスを開発しましたorg.springframework.data.jpa.repository.JpaRepositoryMemberRepositoryメソッドがあります:

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

結果は、Spring キャッシュ抽象化によってキャッシュされます (によってサポートされますConcurrentMapCache)。

私が抱えている問題は、結果が最初にdbから取得され、2回目にキャッシュから取得されることを主張する統合テスト(hsqldbに対して)を書きたいということです。

私は当初、jpa インフラストラクチャ (エンティティ マネージャーなど) をモックすることを考えていましたが、エンティティ マネージャーは 2 回目には呼び出されないと何らかの形で主張しましたが、難しすぎる/面倒だと思われます ( https://stackoverflow.com/a/23442457/536299を参照)。 )。

で注釈が付けられた Spring Data Repository メソッドのキャッシング動作をテストする方法について、誰かがアドバイスを提供できますか@Cacheable?

4

2 に答える 2

85

キャッシングなどの技術的な側面をテストする場合は、データベースをまったく使用しないでください。ここで何をテストしたいかを理解することが重要です。まったく同じ引数を使用した呼び出しでは、メソッド呼び出しが回避されるようにする必要があります。データベースに面するリポジトリは、このトピックとは完全に直交する側面です。

これが私がお勧めするものです:

  1. 宣言型キャッシングを構成する統合テストをセットアップします (または、実稼働構成から必要なビットとピースをインポートします。
  2. リポジトリのモック インスタンスを構成します。
  3. モックの予想される動作を設定するテスト ケースを作成し、メソッドを呼び出して、それに応じて出力を検証します。

サンプル

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

ご覧のとおり、ここでは少し過剰なテストを行っています。

  1. 最も関連性の高いチェックは、2 番目の呼び出しが最初のオブジェクトを返すことだと思います。それがキャッシングのすべてです。同じキーを使用した最初の 2 つの呼び出しは同じオブジェクトを返しますが、異なるキーを使用した 3 番目の呼び出しは、リポジトリでの 2 番目の実際の呼び出しになります。
  2. キャッシュに最初のキーの値が実際にあることを確認することで、テスト ケースを強化します。それを拡張して、実際の値を確認することもできます。一方で、アプリケーション レベルの動作ではなく、メカニズムの内部をテストする傾向があるため、それを避けても問題ないと思います。

重要ポイント

  1. コンテナーの動作をテストするためにインフラストラクチャを用意する必要はありません。
  2. テスト ケースの設定は簡単で簡単です。
  3. 適切に設計されたコンポーネントを使用すると、単純なテスト ケースを記述でき、テストのための統合作業が少なくて済みます。
于 2014-06-15T11:55:57.373 に答える