spring-bootkotlinspring-boot-testmockmvc

Spring boot 2->3 migration: MockMVC tests broke down


The project used to be in Spring Boot 2.6.1, Java 11; now migrating to Spring Boot 3.5.4, Java 17. Here's a minimal test that doesn't work in our environment:

import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.context.junit.jupiter.SpringExtension
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.content
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
import org.springframework.test.web.servlet.setup.MockMvcBuilders
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.context.WebApplicationContext

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ExtendWith(SpringExtension::class)
class MyTest(
    @Autowired
    private val ctx: WebApplicationContext
) {

    @RestController
    class MyController {
        @GetMapping("/foo")
        fun foo() = "bar"
    }

    private lateinit var mockMvc: MockMvc

    @BeforeEach
    fun setup() {
        mockMvc = MockMvcBuilders.webAppContextSetup(ctx).build()
    }

    @Test
    fun dbg() {
        println("Context ${ctx::class.qualifiedName}")
        println("mockmvc: ${ctx.getBeansOfType(MockMvc::class.java)}")
    }

    @Test
    fun foo() {
        mockMvc.perform(get("/foo")).andExpect(status().isOk).andExpect(content().string("bar"))
    }
}

this gives org.junit.jupiter.api.extension.ParameterResolutionException: Failed to resolve parameter [org.springframework.web.context.WebApplicationContext ctx] in constructor [public com.example.MyTest(org.springframework.web.context.WebApplicationContext)]: No qualifying bean of type 'org.springframework.web.context.WebApplicationContext' available: expected at least 1 bean which qualifies as autowire candidate

With this code (this is closer to what we had working originally):

import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.context.junit.jupiter.SpringExtension
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.content
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
@ExtendWith(SpringExtension::class)
class MyTest {

    @RestController
    class MyController {
        @GetMapping("/foo")
        fun foo() = "bar"
    }

    @Autowired
    private lateinit var mockMvc: MockMvc

    @Test
    fun foo() {
        mockMvc.perform(get("/foo")).andExpect(status().isOk).andExpect(content().string("bar"))
    }
}

the error is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'com.example.MyTest': Unsatisfied dependency expressed through field 'mockMvc': No qualifying bean of type 'org.springframework.test.web.servlet.MockMvc' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)} (and that was exactly the error I was getting for ogirinal tests).

Please suggest what can I be missing. Dependency implementation("org.springframework.boot:spring-boot-starter-web") is present.


Solution

  • Figured it out eventually. The company uses a complicated system of profiles, and the profile used for tests contains a (well-hidden) spring.main.web-application-type: none. Which earlier did not prevent MockMVC from autowiring somehow. But now this property is parsed correctly, and the application is not considered a web application, so no WebApplicationContext, so no MockMVCs. The solution is to set type to servlet.