0

I have a simple repository and it's interface written in Kotlin to get a list of sites from db; and I cache response with Spring cache:

interface IRepository {
  fun sites(): List<String>
}

@Repository
class Repository(private val jdbcTemplate: NamedParameterJdbcTemplate) : IRepository {
  private val sites = "SELECT DISTINCT siteId FROM sites"

  @Cacheable(value = ["sites"], key = "sites")
  override fun sites(): List<String> = jdbcTemplate.jdbcTemplate.queryForList(sites, String::class.java)
}

Now I want to test that caching is actually working. As base for test I used How to test Spring's declarative caching support on Spring Data repositories? but direct implementation resulted in error of repository being a proxy and not repository. So my current attempt is:

@ContextConfiguration
@ExtendWith(SpringExtension::class)
class RepositoryCacheTests {
  @MockBean
  private lateinit var repository: Repository

  @Autowired
  private lateinit var cache: CacheManager

  @EnableCaching
  @TestConfiguration
  class CachingTestConfig {
    @Bean
    fun cacheManager(): CacheManager = ConcurrentMapCacheManager("sites")
  }

  @Test
  fun `Sites is cached after first read`() {
    // Arrange
    whenever(repository.sites()).thenReturn(listOf(site, anotherSite))

    repository.sites()

    // Assert
    assertThat(cache.getCache("sites")?.get("sites")).isNotNull
  }

But cache is empty and not populated after first read. What am I missing in my setup?

Update:

Using George's suggestion I updated the test (and code for easier mocking). I also had to add @Bean for repository in configuration because of Could not autowire. No beans of 'Repository' type found. without it.

  @Cacheable(value = ["sites"], key = "'sites'")
  override fun sites(): List<String> = jdbcTemplate.query(sites) { rs, _ -> rs.getString("siteId") }
@ContextConfiguration
@ExtendWith(SpringExtension::class)
class RepositoryCacheTests {
  @MockBean
  private lateinit var jdbcTemplate: NamedParameterJdbcTemplate

  @Autowired
  private lateinit var repository: Repository

  @Autowired
  private lateinit var cache: CacheManager

  @EnableCaching
  @TestConfiguration
  class CachingTestConfig {
    @Bean
    fun testRepository(jdbcTemplate: NamedParameterJdbcTemplate): Repository = Repository(jdbcTemplate)

    @Bean
    fun cacheManager(): CacheManager = ConcurrentMapCacheManager("sites")
  }

  @Test
  fun `Sites is cached after first read`() {
    whenever(jdbcTemplate.query(any(), any<RowMapper<String>>())).thenReturn(listOf(site, anotherSite))
    repository.sites()
    assertThat(cache.getCache("sites")?.get("sites")).isNotNull
    repository.sites()
    verify(jdbcTemplate, times(1)).query(any(), any<RowMapper<String>>())
  }
}

Now the test does not even start:

Error creating bean with name 'RepositoryCacheTests': Unsatisfied dependency expressed through field 'repository'; nested exception is org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'testRepository' is expected to be of type 'Repository' but was actually of type 'com.sun.proxy.$Proxy52'

Update 2:

As George points, solution is (https://stackoverflow.com/a/44911329/492882 and https://stackoverflow.com/a/44911329/492882)

  @Autowired
  private lateinit var repository: IRepository
Sergei G
  • 1,550
  • 3
  • 24
  • 44
  • 1. What is `repository.read(site, locale)`? This method does not exist in the `Repository` class. 2. Results of the `repository.sites()` call are stored in the "site" cache under the "site" key while you are trying to retrieve it from the "templates" key. – Mafor Aug 14 '20 at 15:07
  • Sorry! I tried to simplify code for this question as much as possible and copied wrong method name. I updated the question (it still stands) – Sergei G Aug 14 '20 at 15:51

1 Answers1

1

You are mocking your test subject Repository repository. This should be the real object initialized by Spring so it has caching. You need to mock the JdbcTemplate that your test subject is calling.

I don't really know kotlin syntax, so bear with me. Here's how your test should look like:

@ContextConfiguration
@ExtendWith(SpringExtension::class)
class RepositoryCacheTests {
  @MockBean
  private lateinit jdbcTemplate: NamedParameterJdbcTemplate
  @Autowired
  private lateinit var repository: IRepository

  @Autowired
  private lateinit var cache: CacheManager

  @EnableCaching
  @TestConfiguration
  class CachingTestConfig {
    @Bean
    fun cacheManager(): CacheManager = ConcurrentMapCacheManager("sites")
  }

  @Test
  fun `Sites is cached after first read`() {
    // Arrange
    whenever(jdbcTemplate.queryForList(any(), String::class.java)).thenReturn(listOf(site, anotherSite))

    repository.sites()

    // Assert
    assertThat(cache.getCache("sites")?.get("sites")).isNotNull

    //Execute again to test cache.
    repository.sites()
    //JdbcTemplate should have been called once.
    verify(jdbcTemplate, times(1)).queryForList(any(), String::class.java)
  }
George
  • 2,820
  • 4
  • 29
  • 56
  • What you say makes sense! I tried to follow your advice and failed :( – Sergei G Aug 14 '20 at 17:21
  • What exactly failed? – George Aug 14 '20 at 17:21
  • I updated the question with you suggestions and new failure (BeanNotOfRequiredTypeException for repository) – Sergei G Aug 14 '20 at 17:26
  • According to this https://stackoverflow.com/questions/14509142/beannotofrequiredtypeexception-due-to-autowired-fields You just have to use the interface instead of the class: `repository: IRepository` – George Aug 14 '20 at 17:30
  • Yes, this is the answer. Do you want me to accept your answer right away; or you want to update it so that solution is in the answer and not in the comment? (it's `update 1` with `repository : IRepository` – Sergei G Aug 14 '20 at 18:18
  • Thank you for your help and time! – Sergei G Aug 14 '20 at 18:21