Issue
In one sentence: I want the MockMvc
perform as if I am directly calling the controller.
(P.S. That is syntax sugar. It does not mean I am really calling controllers when integration test.)
Details:
Say we have a Restful controller:
class BookController {
public Book updateBook(int id, Book newBook) {...}
}
A typical Spring integration testing for a RESTful service looks like:
mockMvc.perform(put("/books/1")
.content("{\"id\":1, \"name\": \"ABC\", ...}")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON))
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id", is(1)))
.andExpect(jsonPath("$.name", is("ABC")))
...and more...;
However, can we do things like:
BookController magicBookController = SomeMagic.generate_the_magic_controller();
Book result = magicMvc.perform(magicBookController.updateBook(1, new Book("ABC", ...)));
assertThat(1, result.getId());
assertThat("ABC", result.getName());
...
EDIT: The code above is not simply calling the new BookController().updateBook(...)
method! What I hope is: The generate_the_magic_controller
will generate dynamic proxies (using cglib). Then, when we call magicBookController.updateBook
, actually the dynamically generated code looks like:
Book dynamically_generated_updateBook(int id, Book book) {
String url = magic_assemble_url(id); // will become: "/books/1"
String content = magic_assemble_content(book); // will become: "{name: AAA, ...}"
Something result = mockMvc.perform(put(url)
.content(content)
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON));
return parse_result(result); //parse back into book
}
In one sentence: I want the MockMvc
perform as if I am directly calling the controller. My question:
- Shall I do it? (Or this is a very bad practice?)
- How to do it? I am thinking to hack the part in Spring framework about "finding and parsing controllers", but do not have concrete ideas about how to do that...
EDIT: My goal of testing is as follows. Originally people liked to test code by their eyes (give inputs and look at outputs and assert by their mind). Of course, that is bad. So we write down things like "post to /books and assertThat the outcomes are correct". That is what I want to test. It is actually a bit like E2E test (since this is a restful service) IMHO. (Or is my goal wrong completely?)
EDIT: A typical test in Jersey, which I think (personally) is more elegant than Spring tests:
Profile profile = resources.getJerseyTest()
.target("/v1/profile/" + AuthHelper.VALID_NUMBER_TWO)
.request()
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
.get(Profile.class);
assertEquals(profile.getXXX(), "aaa");
...
Thanks very much for any ideas!
Solution
The point of MockMvc
is to test the REST API are configured correctly using the spring-mvc infrastructure such as things likes an HTTP request can be mapped to and invoke an expected controller method with the correct parameters , the java object returned from the controller method can be serialzied to the correct JSON body in the HTTP response etc. It is about testing if the REST API behave correctly given a HTTP request, so your must specify an HTTP request if you want to use MockMvc
It seems to me that you want to test your rest client such as things like it can send the request to the expected URL with the expected body , and it can deserialise the JSON response back to the java object etc. rather than testing if your REST API is configured properly. If yes , you can stub the REST API using the tools like Wiremock
or okhttp MockWebServer
, and use the rest client as usual to call the stubbed API.
So , the first thing to ask yourself is that what thing do you want to test actually ? Normally we only focus on testing one thing in each time. And it is better to specify the test inputs directly and declaratively without any complex code conversions involved such as convert some given input to the input that are really needed by the test as more conversion codes means more chances to introduce another aspect of error.
So if you want to test if you configure the spring-mvc API correctly (i.e using mockMvc) , it is a bad idea to implement something that translate your generated rest client to the HTTP request that are actually needed to invoke the API.
Answered By - Ken Chan
Answer Checked By - Clifford M. (JavaFixing Volunteer)