Controller Client is a testing library tailored for Spring Boot applications, designed to streamline integration testing of REST controllers. Built on top of Spring MockMvc, it uses dynamic proxies to facilitate intuitive, concise, and type-safe testing, eliminating the repetitive boilerplate associated with MockMvc configurations. With Controller Client, you can directly call your controller methods in test cases, enabling fast and expressive tests that focus purely on application behavior.
- Dynamic Proxy for Controllers: Automatically creates proxies of controller classes, enabling direct, intuitive method calls for tests.
- Flexible Request Customization: Provides hooks for configuring requests and responses, allowing fine-grained control over test conditions.
- Type-Safe Response Mapping: Automatically maps JSON responses back to Java objects, supporting both single objects and lists based on the return type.
This library is still in early stage, so it doesn't support all spring web annotations.
- Add the following dependency to your
build.gradle
project:implementation 'io.github.1grzyb1:controller-client:1.0.9'
- Add
-parameters
to your Java compiler options to enable parameter names in the generated bytecode. For Gradle, you can do this by adding the following to yourbuild.gradle
file:compileJava { options.compilerArgs += ['-parameters'] }
Create a proxy instance of your controller and invoke methods directly
Underneath it uses MockMvc
to perform the request and map the response to the return type of the method.
@AutowireControllerClient
private ExampleController exampleController;
@Test
void basicGet() {
var response = exampleController.exampleMethod();
assertThat(response.message()).isEqualTo("Hello world!");
}
Without controller client it would look like this
@Autowired private MockMvc mockMvc;
@Autowired private ObjectMapper objectMapper;
@Test
void basicGetMockMvc() throws Exception {
String responseContent = mockMvc.perform(get("/example"))
.andExpect(status().isOk())
.andReturn()
.getResponse()
.getContentAsString();
ExampleResponse response = objectMapper.readValue(responseContent, ExampleResponse.class);
assertThat(response.message()).isEqualTo("Hello world!");
}
For following POST request
@PostMapping("/example/body")
public ExampleResponse examplePostMethod(@RequestBody ExampleRequest request) {
return new ExampleResponse(request.getMessage());
}
Controller client usage would look like this
@AutowireControllerClient
private ExampleController exampleController;
@Test
void postWithBody() {
var request = new ExampleRequest("Test message");
var response = exampleController.bodyExample(request);
assertThat(response.message()).isEqualTo("Received: Test message");
}
Without controller client it would look like this
@Test
void testBodyExample() throws Exception {
var exampleRequest = new ExampleRequest("Test message");
var requestJson = objectMapper.writeValueAsString(exampleRequest);
var requestBuilder =
MockMvcRequestBuilders.request(HttpMethod.POST, "/example/body")
.content(requestJson)
.contentType(MediaType.APPLICATION_JSON);
var responseContent = mockMvc.perform(requestBuilder)
.andExpect(status().isOk())
.andReturn()
.getResponse()
.getContentAsString();
var response = objectMapper.readValue(responseContent, ExampleResponse.class);
assertThat(response.message()).isEqualTo("Received: Test message");
}
Sometimes you will need to add some sort of customization to injected proxy.
To do this, you can provide class that implements ControllerClientAnnotationCustomizer
.
For example let's say that you need to add csrf token too each request:
@AutowireControllerClient(customizer = CsrfControllerClientCustomizer.class)
private ExampleController exampleController;
public class CsrfControllerClientCustomizer implements ControllerClientAnnotationCustomizer {
@Override
public ControllerClientBuilder<Object> customize(ControllerClientBuilder<Object> builder) {
return builder.customizeRequest(request -> request.with(csrf()));
}
}
You can check more examples in the example package.