I'm working in a Kotlin application that uses Spring Boot, and one of the Controller classes uses Spring Session with JDBC to persist session IDs:
@RestController
@RequestMapping("/health")
class HealthController {
// healthCheck returns true iff the application is healthy.
@GetMapping
fun healthCheck(session: HttpSession): Map<String, Any> {
val resp = LinkedHashMap<String, Any>()
resp["status"] = true
resp["sessionId"] = session.id
println("health check successful")
return resp
}
}
Every time the health endpoint is called, it saves a session in the underlying application DB that is configured with JDBC if the session does not exist, which is expected behavior:
spring:
datasource:
url: jdbc:postgresql://${DB_HOST:localhost}:5432/${DB_NAME:}
username: ${DB_USERNAME:postgres}
password: ${DB_PASSWORD:password}
session:
store-type: jdbc
However, when I run my unit test, that saves the session data in my underlying DB as well:
@SpringBootTest
@AutoConfigureMockMvc
class HealthControllerTest @Autowired constructor(private val mockMvc: MockMvc) {
// TODO(shubham): Modify unit test to use mock DB under the hood instead of configured app DB
@Test
fun `healthCheck should return status true`() {
mockMvc.perform(get("/health"))
.andExpect(status().isOk)
.andExpect(jsonPath("$.status").value(true))
}
}
How can I modify my unit test to use a mock DB under the hood instead of the configured application DB?
I have tried mocking the JDBCOperations as well as the JDBCTemplate classes hoping that they would be used by Spring Session, but those get ignored in my unit test and don't get mocked (i.e. the underlying DB is still being used).
Examples:
- Using
NamedParameterJdbcTemplatewith the@Mockannotation:
@SpringBootTest
@AutoConfigureMockMvc
class HealthControllerTest @Autowired constructor(private val mockMvc: MockMvc) {
@Mock
private lateinit var template: NamedParameterJdbcTemplate
// TODO(shubham): Modify unit test to use mock DB under the hood instead of configured app DB
@Test
fun `healthCheck should return status true`() {
// mock behavior of template
Assertions.assertNotNull(template)
`when`(template.update(any(String::class.java), any(MapSqlParameterSource::class.java)))
.thenAnswer {
1
}
mockMvc.perform(get("/health"))
.andExpect(status().isOk)
.andExpect(jsonPath("$.status").value(true))
// assert that the mock was called with the specified args
verify(template).update(any(String::class.java), any(MapSqlParameterSource::class.java))
}
}
Error:
Wanted but not invoked:
template.update(
<any java.lang.String>,
<any org.springframework.jdbc.core.namedparam.MapSqlParameterSource>
);
-> at org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate.update(NamedParameterJdbcTemplate.java:337)
Actually, there were zero interactions with this mock.
- Using
NamedParameterJdbcTemplatewith the@MockBeanannotation:
...
@MockBean
private lateinit var template: NamedParameterJdbcTemplate
...
Error:
Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'userRepository' defined in com.app.server.repository.UserRepository defined in @EnableJdbcRepositories declared on JdbcRepositoriesRegistrar.EnableJdbcRepositoriesConfiguration: Cannot resolve reference to bean 'org.springframework.data.jdbc.core.mapping.JdbcMappingContext' while setting bean property 'mappingContext'
- Using
JDBCTemplatewith the@Mockannotation:
...
@Mock
private lateinit var template: JDBCTemplate
Error:
Wanted but not invoked:
template.update(
<any java.lang.String>,
<any org.springframework.jdbc.core.namedparam.MapSqlParameterSource>
);
-> at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:1024)
Actually, there were zero interactions with this mock.
- Using
JDBCTemplatewith the@MockBeanannotation:
...
@MockBean
private lateinit var template: JDBCTemplate
...
Error:
Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'userRepository' defined in com.app.server.repository.UserRepository defined in @EnableJdbcRepositories declared on JdbcRepositoriesRegistrar.EnableJdbcRepositoriesConfiguration: Cannot resolve reference to bean 'org.springframework.data.jdbc.core.mapping.JdbcMappingContext' while setting bean property 'mappingContext'
- Using
JdbcOperationswith the@Mockannotation:
...
@Mock
private lateinit var jdbcOperations: JdbcOperations
Error:
Wanted but not invoked:
jdbcOperations.update(
<any java.lang.String>,
<any org.springframework.jdbc.core.namedparam.MapSqlParameterSource>
);
-> at com.ordrsmart.server.controller.HealthControllerTest.healthCheck should return status true(HealthControllerTest.kt:63)
Actually, there were zero interactions with this mock.
- Using
JdbcOperationswith the@MockBeanannotation:
...
@MockBean
private lateinit var jdbcOperations: JdbcOperations
...
Error:
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of method namedParameterJdbcTemplate in org.springframework.boot.autoconfigure.jdbc.NamedParameterJdbcTemplateConfiguration required a bean of type 'org.springframework.jdbc.core.JdbcTemplate' that could not be found.
Action:
Consider defining a bean of type 'org.springframework.jdbc.core.JdbcTemplate' in your configuration.