18

I wrote one unit test that tests UsersController. UsersControllerTest.findUser is working fine, but UsersControllerTest.insertGetModifyDelete it doesn't.

In the log of the test I can see that the POST request doesn't match any method of UsersController, but I don't understand why. Could you help me with this, please?.

This is my rest java class:

@RestController
@RequestMapping("/users")
public class UsersController {

    private final UsersService usersService;

    @Autowired
    public UsersController(UsersService usersService) {
        this.usersService = usersService;
    }

    @GetMapping(value="/{email}", produces="application/json")
    public User get(@PathVariable @Email String email) {
        return usersService.findByEmail(email);
    }

    @PostMapping(consumes="application/json", produces="application/json")
    @ResponseBody
    public ResponseEntity<String> insert(@RequestBody @Valid User user){
        usersService.insert(user);
        return ResponseEntity.ok(user.getEmail());
    }

    @DeleteMapping(value="/{email}", consumes="application/json", produces="application/json")
    public ResponseEntity<String> delete(@PathVariable @Email String email) {
        usersService.delete(email);
        return ResponseEntity.ok(email);
    }

    @PutMapping(value="/{email}", consumes="application/json", produces="application/json")
    public ResponseEntity<User> update(@PathVariable @Email String email, @RequestBody @Valid User user) {
        usersService.update(email, user);
        return ResponseEntity.ok(user);
    }

}

I have one test with 2 methods. One is working returning http code 200, but the other returns 403.

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
@WithMockUser(username = "user", roles = "USER")
public class UsersControllerTest {


    @Autowired
    private MockMvc mockMvc;

    @Test
    public void findUser() throws Exception {
        mockMvc.perform(get("/users/{email}", new Object[] {"boy@test.com"})).andExpect(status().isOk()).andExpect(jsonPath("$.email", equalTo("boy@test.com"))).andExpect(jsonPath("$.userName", equalTo("boy")));
    }

    @Test
    public void insertGetModifyDelete() throws Exception {
        User user = new User("userName", "email@email.com");
        ObjectMapper mapper = new ObjectMapper();
        String json = mapper.writeValueAsString(user);
        mockMvc.perform(post("/users").content(json).contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)).andExpect(status().isOk());
        mockMvc.perform(put("/users/{email}", new Object[] {user.getEmail()}).content(json)).andDo(print()).andExpect(status().isOk());
        mockMvc.perform(delete("/users/{email}", new Object[] {user.getEmail()}).content(json)).andDo(print()).andExpect(status().isOk());
    }

}

And this is the output of the test:

MockHttpServletRequest:
      HTTP Method = GET
      Request URI = /users/boy@test.com
       Parameters = {}
          Headers = {}
             Body = null
    Session Attrs = {SPRING_SECURITY_CONTEXT=org.springframework.security.core.context.SecurityContextImpl@ca25360: Authentication: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@ca25360: Principal: org.springframework.security.core.userdetails.User@36ebcb: Username: user; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_USER; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: ROLE_USER}

Handler:
             Type = es.tododev.fairtasks.rest.UsersController
           Method = public es.tododev.fairtasks.dto.User es.tododev.fairtasks.rest.UsersController.get(java.lang.String)

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = null

ModelAndView:
        View name = null
             View = null
            Model = null

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 200
    Error message = null
          Headers = {Content-Disposition=[inline;filename=f.txt], Content-Type=[application/json;charset=UTF-8], 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 = application/json;charset=UTF-8
             Body = {"userName":"boy","email":"boy@test.com"}
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

MockHttpServletRequest:
      HTTP Method = POST
      Request URI = /users
       Parameters = {}
          Headers = {Content-Type=[application/json;charset=UTF-8], Accept=[application/json]}
             Body = {"userName":"userName","email":"email@email.com"}
    Session Attrs = {org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository.CSRF_TOKEN=org.springframework.security.web.csrf.DefaultCsrfToken@66944c7c, SPRING_SECURITY_CONTEXT=org.springframework.security.core.context.SecurityContextImpl@ca25360: Authentication: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@ca25360: Principal: org.springframework.security.core.userdetails.User@36ebcb: Username: user; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_USER; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: ROLE_USER}

Handler:
             Type = null

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = null

ModelAndView:
        View name = null
             View = null
            Model = null

FlashMap:
       Attributes = 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 = []
[ERROR] Tests run: 2, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 6.155 s <<< FAILURE! - in es.tododev.fairtasks.rest.UsersControllerTest
[ERROR] insertGetModifyDelete(es.tododev.fairtasks.rest.UsersControllerTest)  Time elapsed: 0.028 s  <<< FAILURE!
java.lang.AssertionError: Status expected:<200> but was:<403>
        at es.tododev.fairtasks.rest.UsersControllerTest.insertGetModifyDelete(UsersControllerTest.java:48)
ravenskater
  • 702
  • 1
  • 7
  • 20
  • At first glance it seems that the `value` attribute is missing in the line `@PostMapping(consumes="application/json", produces="application/json")`. In the trace you can see that the request goes straight to `users` which is not mapped in the controller. – tom1299 Nov 20 '18 at 07:07
  • It is mapped in the controller: @RequestMapping("/users") As the request is going to /users and the POST method, spring should be able to match the correct method. However I already tried to add value in the post mapping, but still it doesn't work. – ravenskater Nov 21 '18 at 06:08

3 Answers3

33

You can try to debug this program.i think probleam is happend in "mockMvc" object is not autowired.mockMvc object should load from WebApplicationContext in before program run.

@Autowired
private WebApplicationContext webApplicationContext

@Before()
public void setup()
{
    //Init MockMvc Object and build
    mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
Arshad Ali
  • 3,082
  • 12
  • 56
  • 99
XuanGang
  • 456
  • 4
  • 4
  • It worked. In my spring boot version 2.x I did it like this: mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build(); But I don't understand why the method UsersControllerTest.findUser() worked autowiring MockMvc – ravenskater Nov 21 '18 at 06:14
  • 2
    It worked for me as well. It is so weird that having many GET and PATCH endopoints tested, it only passed the GETs when using only @Autowired MockMvc, but with this solution of `@Autowired WebApplicationContext webApplicationContext;` + `mvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();` all passed. (spring-boot.version: 2.1.3.RELEASE + spring-security-test.version: 5.1.4.RELEASE) – wizardInCloud Mar 30 '19 at 20:38
  • 1
    What is the trick here? – fuat Nov 24 '22 at 14:53
31

I got around it with @AutoConfigureMockMvc(addFilters = false). Please make sure that this won't compromise other functionality on your HTTP stack.

skrymir1
  • 557
  • 6
  • 5
7

When working with CSRF protection, a CSRF token must be provided or a 403 status will be returned. I fixed this in my testing by specifically adding a CSRF token to any non-safe HTTP method (POST, PUT, DELETE) as per the documentation.

import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;

[...]

mvc.perform(post("/").with(csrf()));
mvc.perform(put("/").with(csrf()));
mvc.perform(delete("/").with(csrf()));
ckesplin
  • 1,284
  • 1
  • 9
  • 15