0

I'm trying to test a routerfunction in webmvc using kotest and mockk. I think the way it's written that only the router function and the test itself should be executed. Everything else is mocked. The routerfunction is configured as follows:

@Configuration
class DownloadRoutes(private val dnldCtrllr : DownloadController,
) {
    private var baseUrl : String = "download"

    @Bean
    fun router(): RouterFunction<ServerResponse> {
        return router {
            baseUrl.nest{
                accept(MediaType.TEXT_PLAIN).nest {
                    "/asset_request".nest {
                        POST(dnldCtrllr::downloadPost)
                    }
                }
            }
        }
    }
}

The test uses the WebMvcTest annotation. I mock the POST handler so that if it is called at it's entry point, it simply returns a status of OK.

The test looks as follows:

@WebMvcTest
@ContextConfiguration(classes = [DownloadRoutes::class])
class DownloadRoutesTest( @Autowired val mockMvc : MockMvc,
                          @MockkBean val mockDwnLd : DownloadController,
                          @Autowired val ctx : ApplicationContext
) : DescribeSpec({
    describe("Download Service Routes") {
        it("should route POSTs to the appropriate handler") {
            val bean = ctx.getBean("router")
            println(bean.toString())

            every { mockDwnLd.downloadPost(any())} returns ServerResponse.status(HttpStatus.OK).build()

            mockMvc.perform(MockMvcRequestBuilders
                            .post("/download/asset_request")
                            .accept(MediaType(MediaType.TEXT_PLAIN))
            )
                .andDo(MockMvcResultHandlers.print())       // prints the request and response; for debugging only
                .andExpect(MockMvcResultMatchers.status().isOk)
        }
    }
})

It doesn't pass. I've printed the router bean obtained from the application context to be sure it's there and I think it looks right. I also added a print to the mockMvc chain so I can see what happens.

Here's the prints:

/download => {
 Accept: text/plain => {
  /asset_request => {
   POST -> org.springframework.web.servlet.function.RouterFunctionDslKt$sam$org_springframework_web_servlet_function_HandlerFunction$0@1d9488b
   GET -> org.springframework.web.servlet.function.RouterFunctionDslKt$sam$org_springframework_web_servlet_function_HandlerFunction$0@dca44a2
  }
 }
}

MockHttpServletRequest:
      HTTP Method = POST
      Request URI = /download/asset_request
       Parameters = {}
          Headers = [Accept:"text/plain"]
             Body = null
    Session Attrs = {org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository.CSRF_TOKEN=org.springframework.security.web.csrf.DefaultCsrfToken@3840636a}

Handler:
             Type = null


MockHttpServletResponse:
           Status = 403
    Error message = Forbidden
          Headers = [X-Content-Type-Options:"nosniff", X-XSS-Protection:"1; mode=block", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Frame-Options:"DENY"]
     Content type = null
             Body = 
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

Status expected:<200> but was:<403>
Expected :200
Actual   :403

I'm assuming that the 403 means it's never getting to the router function. Does the "handler = null" mean that the router is not getting invoked (why)? Does the mockMvc not properly deal with the router function (as opposed to the old annotation methods)? I'm assuming it's the mocked DownloadController that's getting injected into the DownloadRoutes, but I'm not entirely convinced.

Anyone have any thoughts?

1 Answers1

2

I should have paid more attention to the fact it was a 403 error. It wasn't saying it couldn't find the route. It was saying that I didn't have access to the route. That's because I had security enabled for the app (I had "spring-boot-starter-security" in the list of dependencies). I added

@AutoConfigureMockMvc(addFilters = false)

to the annotations. This prevents the security filters from being added and the test now passes. https://www.baeldung.com/spring-security-disable-profile may be an alternative.

  • I don't know why but I had to use `@AutoConfigureMockMvc` and `@SpringBootTest` in my controller test to get it to configure MockMvc correctly. Using the `@WebMvcTest` didn't work at all so I must have been missing some step. I did not need to use the addFilters = flase like you did, just the annotation worked. – Dani May 19 '22 at 20:58