Shubham Goenka
Shubham Goenka

Reputation: 11

How to Mock JDBC in Unit Test for Spring Session

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:

  1. Using NamedParameterJdbcTemplate with the @Mock annotation:
@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.
  1. Using NamedParameterJdbcTemplate with the @MockBean annotation:
...
@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'
  1. Using JDBCTemplate with the @Mock annotation:
...
@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.
  1. Using JDBCTemplate with the @MockBean annotation:
...
@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'
  1. Using JdbcOperations with the @Mock annotation:
...
@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.
  1. Using JdbcOperations with the @MockBean annotation:
...
@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.

Upvotes: 1

Views: 210

Answers (0)

Related Questions