在 Kotlin 和 JUnit5 中测试 Spring Boot 缓存

Posted

技术标签:

【中文标题】在 Kotlin 和 JUnit5 中测试 Spring Boot 缓存【英文标题】:Test Spring Boot cache in Kotlin and JUnit5 【发布时间】:2020-12-04 10:57:24 【问题描述】:

我有一个简单的存储库,它是用 Kotlin 编写的用于从 db 获取站点列表的界面;我用 Spring 缓存缓存响应:

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)

现在我想测试一下缓存是否真的有效。作为测试的基础,我使用了How to test Spring's declarative caching support on Spring Data repositories?,但直接实现导致存储库是代理而不是存储库的错误。所以我目前的尝试是:

@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
  

但是缓存是空的并且在第一次读取后没有填充。我的设置中缺少什么?

更新:

根据 George 的建议,我更新了测试(以及更容易模拟的代码)。我还必须在配置中为存储库添加@Bean,因为没有Could not autowire. No beans of 'Repository' type found.

  @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>>())
  

现在测试甚至还没有开始:

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'

更新 2:

正如乔治指出的那样,解决方案是(https://***.com/a/44911329/492882 和 https://***.com/a/44911329/492882)

  @Autowired
  private lateinit var repository: IRepository

【问题讨论】:

1. repository.read(site, locale) 是什么? Repository 类中不存在此方法。 2. repository.sites() 调用的结果存储在“site”键下的“site”缓存中,而您正试图从“templates”键中检索它。 对不起!我试图尽可能简化这个问题的代码并复制错误的方法名称。我更新了问题(它仍然存在) 【参考方案1】:

你在嘲笑你的测试对象Repository repository。这应该是由 Spring 初始化的真实对象,因此它具有缓存。您需要模拟您的测试对象正在调用的JdbcTemplate

我真的不知道 kotlin 语法,所以请耐心等待。您的测试应如下所示:

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

【讨论】:

你说的有道理!我试图听从你的建议但失败了:( 究竟是什么失败了? 我用你的建议和新的失败更新了问题(存储库的 BeanNotOfRequiredTypeException) 根据这个***.com/questions/14509142/…你只需要使用接口而不是类:repository: IRepository 是的,这就是答案。你想让我马上接受你的回答吗?或者您想更新它以便解决方案在答案中而不是在评论中? (这是update 1repository : IRepository

以上是关于在 Kotlin 和 JUnit5 中测试 Spring Boot 缓存的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 JUnit 5 在 Kotlin 中创建 TestContainers 基测试类

如何在用 Kotlin 编写的 JUnit 5 测试类中注入 Spring bean?

我是不是需要使用 Kotlin Junit5 和 Spring Boot 将 Spring 注释复制到内部类?

Gradle 没有找到我使用 Kotlin 和 JUnit 5 进行的测试

Kotlin + SpringBootTest + Junit 5 + AutoConfigureMockMvc:测试通过时应该失败(似乎@BeforeEach没有生效)

使用 spring boot gradle 插件无法使用依赖管理执行 junit5 测试