Testing routingfunction with webmvc in springmvc in kotlin - spring-boot

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?

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.

Related

Spring runner test return 404 for API test

I am writing API test cases for one of my controllers, but it is resulting with a 404.
I thought it would be a typo but it is not. Below are the code snippets.
RestController: package: com.x.y.address.controller (src/main)
#RestController
public class AddressInternalController {
#PostMapping(value = "/v1/address-service/internal/company/address", produces = "application/json;charset=UTF-8")
#ResponseStatus(OK)
public #ResponseBody ResponseEntity<AddressModel> createCompanyAddress()
throws AddressException, BadRequestException {
return ok("SUCCESS");
}
}
My Test class: package com.x.y.address.controller (src/test)
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = TestApp.class, initializers = ConfigFileApplicationContextInitializer.class)
#WebMvcTest(controllers = AddressInternalController.class, secure = false)
public class AddressInternalControllerTest {
#Autowired
private MockMvc mvc;
#Before
public void init() {}
#Test
public void createAddressTest_when_invalid_company() throws Exception {
this.mvc.perform(post("/v1/address-service/internal/company/address").contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}
}
My app uses spring security and to bypass that I have created a TestAPP class so that it will help me build only the config without security.
TestApp: package com.x.y.address (src/test)
#EnableAutoConfiguration(exclude = { SecurityAutoConfiguration.class})
// #ComponentScan({"com.x.y.address.controller.AddressInternalController"})
public class TestApp {
}
Above are the structure of the class.
Initially I thought may be the program does not scan the controller package and hence the 404. Hence added the componentScan. But that did not help.
Searched through a lot of stack over flow but most of the 404 are due to a type but it is not in my case.
Error log:
MockHttpServletRequest:
HTTP Method = POST
Request URI = /v1/address-service/internal/company/address
Parameters = {}
Headers = {Content-Type=[application/json]}
Body = <no character encoding set>
Session Attrs = {}
Handler:
Type = org.springframework.web.servlet.resource.ResourceHttpRequestHandler
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 404
Error message = null
Headers = {}
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
java.lang.AssertionError: Status
Expected :200
Actual :404
Any help shall be greatly appreciated.
I replaced:
#EnableAutoConfiguration(exclude = { SecurityAutoConfiguration.class})
// #ComponentScan({"com.x.y.address.controller.AddressInternalController"})
public class TestApp {
}
with:
#SpringBootApplication(exclude = { SecurityAutoConfiguration.class})
// #ComponentScan({"com.x.y.address.controller.AddressInternalController"})
public class TestApp {
}
and it worked.
UPDATE 1:
I noticed, in your #ComponentScan you use the path to the class itself, but you should point to the package with your controller. If you want to specify a class, use basePackageClasses property of #ComponentScan

Spring Boot Web Test - 404 Not found

I am trying to test the Controller of a very simple GET request with #WebMvcTest but I'm getting 404 instead of 200 and the console does not give me anything useful to understand what is going on.
I've put a breakpoint at the beginning of the controller but it never arrives. If I run the application, the endpoint works as expected.
Here's my controller:
#RestController
public class RegistroClienteController {
#GetMapping("/api/registro-cliente")
public ResponseEntity<Void> crearCliente() {
return new ResponseEntity<Void>(HttpStatus.OK);
}
}
And here's my test:
#RunWith(SpringRunner.class)
#WebMvcTest(RegistroClienteController.class)
#ContextConfiguration(classes = { SecurityConfig.class })
public class RegistroClienteControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
UserDetailsService userDetailsService;
#Test
public void test() throws Exception {
//#formatter:off
mockMvc
.perform(get("/api/registro-cliente"))
.andExpect(status().isOk());
//#formatter:on
}
}
And console's output:
MockHttpServletRequest:
HTTP Method = GET
Request URI = /api/registro-cliente
Parameters = {}
Headers = {}
Handler:
Type = org.springframework.web.servlet.resource.ResourceHttpRequestHandler
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 404
Error message = null
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 = [[Cookie#624b3544 name = 'XSRF-TOKEN', value = 'f9d63654-4e21-4d41-b3bb-6767703268b5', comment = [null], domain = [null], maxAge = -1, path = '/', secure = false, version = 0, httpOnly = false]]
I was having the same error and after hours of searching found that the error is due to controller not being registered. The problem is described here.
Apparently, the following is not enough.
#WebMvcTest(controllers = {<ControllerToTest>.class})
You need to do,
#WebMvcTest(controllers = {<ControllerToTest>.class})
#Import(<ControllerToTest>.class)
I can try to change the annotations
#WebMvcTest(RegistroClienteController.class)
to
#SpringBootTest(classes = {your application class}.class)

How to test if a controller method forwards the requests to a specific URL?

In my Spring Boot application I have the following controller with a single method that redirects all HTML5 routes to the root URL**:
#Controller
public class RedirectController {
#RequestMapping(value = "/**/{path:[^\\.]*}")
public String redirect() {
return "forward:/";
}
}
How should I properly test that it works as expected?
Calling the content() method of the MockMvcResultMatchers class doesn't work:
#Test
public void givenPathWithoutDotShouldReturnString() throws Exception {
this.mockMvc.perform(get("/somePath"))
.andExpect(content().string("forward:/"));
}
>>> java.lang.AssertionError: Response content
>>> Expected :forward:/
>>> Actual :
** I found out about this solution from following this Spring tutorial.
When I called the andDo(print()) of the mockMvc class I got the following result:
MockHttpServletResponse:
Status = 200
Error message = null
Headers = {}
Content type = null
Body =
Forwarded URL = /
Redirected URL = null
Cookies = []
Here I realized that Spring doesn't treat return "forward:/"; as a simple String result, but a URL forwarding (in a way it's pretty obvious), so the proper way to write the test is by calling the .andExpect() method with forwardedUrl("/") as an argument:
#Test
public void givenPathWithoutDotShouldReturnString() throws Exception {
this.mockMvc.perform(get("/somePath"))
.andExpect(forwardedUrl("/"));
}
The forwardedUrl() method comes from org.springframework.test.web.servlet.result.MockMvcResultMatchers.

upgrading spring boot with groovy controller returns 406 causing HttpMediaTypeNotAcceptableException

I have a Groovy application that I am dealing with which is having some odd behavior when upgrading from spring-boot 1.3.0.RELEASE to 1.4.0.RELEASE. The controller always returns a 406 on any error and I am not sure what type of content it expects to return. The code is below:
SomeController.groovy:
#RestController
#RequestMapping('/some/mapping')
class SomeController extends AbstractController {
#Autowired
private SomeService someService
#RequestMapping(path = '/abc/{some_param}/some_action', method = RequestMethod.PUT, consumes = MediaType.TEXT_PLAIN_VALUE)
#ResponseStatus(HttpStatus.NO_CONTENT)
#PreAuthorize('isAuthenticated() && (principal.username == #username || principal.admin)')
void setValue(#PathVariable String some_param, #RequestBody String body_content) throws ValidationException, NotFoundException {
handleViolations(validate(AnObject, [some_param: some_param, body: body_content]))
try {
someService.setValue(some_param, body_content)
} catch(AlreadyExistsException e) {
throw new ValidationException([body: 'IN_USE'])
}
}
}
SomeControllerSpec.groovy < The test...
class AccountControllerSpec extends AbstractControllerSpec {
static final BASE_URL = 'http://localhost:8080/api/'
def client = new CustomRESTClient(BASE_URL)
// This test fails
def 'testing api'() {
//Expected 400 bad request but receiving a 406 not acceptable
client.put(
path: "/api/abc/fake_param/some_action",
// The body doesn't conform to the expectations of the API
body: 'blah',
contentType: MediaType.TEXT_PLAIN_VALUE
).status == HttpStatus.SC_BAD_REQUEST
// Exception thrown:
// INFO 22125 --- [tp1838490665-22] c.c.w.c.RestEndpointsConfiguration : org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation
}
}
The Exception in the logs:
INFO 22125 --- [tp1838490665-22] c.c.w.c.RestEndpointsConfiguration : org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation
I have tried many things including setting the expected header type:
client.setHeaders(accept: MediaType.TEXT_PLAIN_VALUE)
I have been trying various other things but to no avail. The exception persists.
Note: The action at the endpoint completes as expected.

Empty content body in checking exception scenarios with mockmvc

Overview:
I am going to test bad request (400) with a customized error message in Spring MVC Test. The test gets 400 as status; however the content body is empty.
The code snippets are as follows:
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
memberServiceController.setMemberDetailsApiController(mockMemberDetailsApiController);
memberServiceController.setResourceMessage(mockResourceMessage);
mockMvc = MockMvcBuilders.webAppContextSetup(context)
.addFilter(new ShallowEtagHeaderFilter())
.apply(documentationConfiguration(restDocumentation))
.build();
}
#Test
public void getMemberDetails_whenStoreIdIsNull_setStatusToBadRequest() throws Exception {
Mockito.doReturn("storeId is empty").when(mockResourceMessage).getMessage(MEMBER_ERROR_INVALID_STOREID);
mockMvc.perform(get(URL)
.header(REQUEST_HEADER_COOKIE, DEFAULT_COOKIE_VALUE)
.param(REQUEST_PARAM_MEMBERSHIP_IDENTIFIER, "MEMBER1"))
.andDo(MockMvcResultHandlers.print())
.andDo(document("memberServices/GetMemberDetailsNullStoreId",
requestHeaders(
headerWithName(REQUEST_HEADER_COOKIE).description(REQUEST_HEADER_COOKIE_DESCRIPTION)
),
requestParameters(
parameterWithName(REQUEST_PARAM_MEMBERSHIP_IDENTIFIER).description(REQUEST_PARAM_MEMBERSHIP_IDENTIFIER_DESCRIPTION)
)))
.andExpect(status().isBadRequest())
.andExpect(content().string(containsString("storeId is empty".toLowerCase())))
.andReturn().getResponse();
}
The raised exception is as follows:
MockHttpServletRequest:
HTTP Method = GET
Request URI = /retailer1/memberDetails
Parameters = {membershipIdentifier=[MEMBER1]}
Headers = {Cookie=[SESSION=67421bc3-36da-4b64-9aca-94edf57211f6]}
Handler:
Type = com.blss.retailServices.memberServices.restServices.MemberRestController
Method = public org.springframework.http.HttpEntity<org.springframework.hateoas.Resource<com.blss.retailServices.memberServices.models.MemberDetailsResponseModel>> com.blss.retailServices.memberServices.restServices.MemberRestController.getMemberDetails(com.blss.retailServices.memberServices.models.MemberModel)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = com.blss.retailServices.InvalidRequestException
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 400
Error message = null
Headers = {}
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
java.lang.AssertionError: Response content
Expected: a string containing "storeid is empty"
but: was ""
The generated response in API Doc with asciidoc is as follows:
HTTP/1.1 400 Bad Request
Question:
Now I would appreciate it if anyone can help me find way to get bad request with my customized exception message ("storeId is empty") in order to be added to generated API documentation and have something like bellow as generated response in API documentation:
HTTP/1.1 400 Bad Request,
storeId is empty
The problem was related to exception handling in my code. I forgot to add GlobalControllerExceptionHandler class which is our exception handler to #SpringApplicationConfiguration in my test class. So after adding it as follows my problem solved:
#SpringApplicationConfiguration(classes = {
MemberRestControllerTest.class,
MemberRestController.class,
ResourceResultProcessor.class,
GlobalControllerExceptionHandler.class
})
public class MemberRestControllerTest {
...
}

Resources