I am new to Spring and trying to write some unit tests for my REST controller. Testing manually with httpie or curl works well, however, with @WebMvcTest, strange things happen.
Here is what happens when I PUT a new user by curl:
$ curl -v -H'Content-Type: application/json' -d@- localhost:8080/api/users <john_smith.json
* Trying 127.0.0.1:8080...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> POST /api/users HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.69.1
> Accept: */*
> Content-Type: application/json
> Content-Length: 102
>
* upload completely sent off: 102 out of 102 bytes
* Mark bundle as not supporting multiuse
< HTTP/1.1 200
< Content-Type: application/json
< Transfer-Encoding: chunked
< Date: Sat, 18 Apr 2020 22:29:43 GMT
<
* Connection #0 to host localhost left intact
{"id":1,"firstName":"John","lastName":"Smith","email":"[email protected]","password":"l33tp4ss"}
As you can see, the Content-Type header is there in the response and the body is indeed the new User.
Below is how I am trying to test the same automatically:
@RunWith(SpringRunner.class)
@WebMvcTest(UserController.class)
public class UserControllerTest {
@Autowired
private MockMvc mvc;
@MockBean
private UserService service;
private final User john = new User("John", "Smith",
"[email protected]",
"s3curep4ss");
@Test
public void givenNoUser_whenCreateUser_thenOk()
throws Exception
{
given(service.create(john)).willReturn(john);
mvc.perform(post("/users")
.contentType(APPLICATION_JSON)
.content(objectToJsonBytes(john)))
.andExpect(status().isOk())
.andExpect(content().contentType(APPLICATION_JSON))
.andExpect(jsonPath("$.id", is(0)))
.andDo(document("user"));
}
}
But what I get is this:
$ mvn test
[...]
MockHttpServletRequest:
HTTP Method = POST
Request URI = /users
Parameters = {}
Headers = [Content-Type:"application/json", Content-Length:"103"]
Body = {"id":0,"firstName":"John","lastName":"Smith","email":"[email protected]","password":"s3curep4ss"}
Session Attrs = {}
Handler:
Type = webshop.controller.UserController
Method = webshop.controller.UserController#create(Base)
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 type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
[ERROR] Tests run: 6, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 11.271 s <<< FAILURE! - in webshop.UserControllerTest
[ERROR] givenNoUser_whenCreateUser_thenOk Time elapsed: 0.376 s <<< FAILURE!
java.lang.AssertionError: Content type not set
at webshop.UserControllerTest.givenNoUser_whenCreateUser_thenOk(UserControllerTest.java:70)
What is happening? Where is the body from the MockHttpServletResponse? I must be missing something, as it seems to act completely differently.
My generic controller class:
public class GenericController<T extends Base>
implements IGenericController<T> {
@Autowired
private IGenericService<T> service;
@Override
@PostMapping(consumes = APPLICATION_JSON_VALUE,
produces = APPLICATION_JSON_VALUE)
public T create(@Valid @RequestBody T entity)
{
return service.create(entity);
}
/* ... Other RequestMethods ... */
}
The actual User controller:
@RestController
@RequestMapping(path="/users")
public class UserController extends GenericController<User> { }
UPDATE 2020-04-22
As suggested, I took generics out of the equation, but it did not help.
Seems like the @WebMvcTest annotation is configuring a UserService bean that is using the real implementation, and your bean is somehow ignored.
We can try to create the UserService bean differently
@RunWith(SpringRunner.class)
@WebMvcTest(UserController.class)
@Import(UserControllerTest.Config.class)
public class UserControllerTest {
@TestConfiguration
static class Config {
@Primary
@Bean
UserService mockedUserService() {
UserService service = Mockito.mock(UserService.class);
given(service.create(john)).willReturn(UserControllerTest.john());
return service;
}
}
static User john() {
return new User("John", "Smith", "[email protected]", "s3curep4ss");
}
...
}
You could also move the stubbing to a @Before method in your tests
@Configuration
public class CommonTestConfig {
@Primary
@Bean
UserService mockedUserService() {
return Mockito.mock(UserService.class)
}
}
@RunWith(SpringRunner.class)
@WebMvcTest(UserController.class)
@Import(CommonTestConfig.class)
public class Test1 {
@Autowired
private UserService userService;
@Before
public void setup() {
given(userService.create(any())).willReturn(user1());
}
}
@RunWith(SpringRunner.class)
@WebMvcTest(UserController.class)
@Import(CommonTestConfig.class)
public class Test2 {
@Autowired
private UserService userService;
@Before
public void setup() {
given(userService.create(any())).willReturn(user2());
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With