This article is an example of Spring Boot's MockMvc. We'll be utilising the code from Test-Driven Development with Spring Boot Starter Test. We'll move our MathFun
code to the service layer and create a REST controller that will call it.
We will not dive into much detail on how to build the project as our focus will be on testing the web layer with MockMvc
. The beauty of MockMvc
is it doesn't start an HTTP server. It handles the incoming HTTP request and hands it off to our controller. Our code is called exactly the same way, just like a real HTTP request. With the power of annotations, Spring's injects MockMvc
.
Create the Project
Create your project, head to Spring Initializr. We'll be using Maven, Java 8, Spring Boot 2.7.7, and with developer tools and web (see image) as dependencies. This is what I've got:
Project Metadata
- Group: com.blogspot.jpllosa
- Artifact: tdd-mockmvc
- Name: TDD Web layer
- Description: Test-Driven Development with MockMvc
- Package name: com.blogspot.jpllosa
When you are ready, import the project to your IDE. I use Spring Tool Suite 4. If you are keen on the finished project, clone it here, github.com/jpllosa/tdd-mockmvc.
Spring Boot MockMvc Example |
Create the Model
Create a package named com.blogspot.jpllosa.model
and create GreatestCommonFactor
. This object will be returned by our controller. Don't worry, Spring will automatically send the response body in JSON format because of the @RestController
annotation.
package com.blogspot.jpllosa.model;
public class GreatestCommonFactor {
Integer firstNumber;
Integer secondNumber;
Integer gcf;
public Integer getFirstNumber() {
return firstNumber;
}
public void setFirstNumber(Integer firstNumber) {
this.firstNumber = firstNumber;
}
public Integer getSecondNumber() {
return secondNumber;
}
public void setSecondNumber(Integer secondNumber) {
this.secondNumber = secondNumber;
}
public Integer getGcf() {
return gcf;
}
public void setGcf(Integer gcf) {
this.gcf = gcf;
}
}
Create the Service
Create a package named com.blogspot.jpllosa.service
and create MathFunService
. This is our service layer, the layer that handles the business logic. The service layer also serves as a traffic cop between the controller and persistence layer. Hence, this bean is annotated with @Service
.
package com.blogspot.jpllosa.service;
import org.springframework.stereotype.Service;
import com.blogspot.jpllosa.model.GreatestCommonFactor;
@Service
public class MathFunService {
public GreatestCommonFactor findGcf(int x, int y) {
int result = getGCF(x, y);
GreatestCommonFactor gcf = new GreatestCommonFactor();
gcf.setFirstNumber(x);
gcf.setSecondNumber(y);
gcf.setGcf(result);
return gcf;
}
public int getGCF(int x, int y) {
return (y == 0) ? x : getGCF(y, x % y);
}
}
Create the Controller
Last but not the least. Create a package named com.blogspot.jpllosa.controller
and create MathFunController
. This is our web layer. GET HTTP requests to the resource /get-gcf are routed here. The method checks for two query parameters and are automatically injected to the method parameters. Then sends it to the service layer for processing. It returns the type GreatestCommonFactor
which is automatically marshalled into a JSON body as mentioned earlier.
package com.blogspot.jpllosa.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.blogspot.jpllosa.model.GreatestCommonFactor;
import com.blogspot.jpllosa.service.MathFunService;
@RestController
public class MathFunController {
@Autowired
private MathFunService service;
@GetMapping("/get-gcf")
public GreatestCommonFactor getGcf(@RequestParam int firstNumber, @RequestParam int secondNumber) {
return service.findGcf(firstNumber, secondNumber);
}
}
Running the App
Now that everything is wired up, we should be able to run the application and see the response. To run the app in STS4, right click, Run As + Spring Boot App.
$ curl -G -d "firstNumber=12" -d "secondNumber=16" http://localhost:8080/get-gcf
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 44 0 44 0 0 1419 0 --:--:-- --:--:-- --:--:-- 1419{"firstNumber":12,"secondNumber":16,"gcf":4}
Above, we are finding the GCF of 12 and 16. The GCF is 4.
$ curl "http://localhost:8080/get-gcf?firstNumber=0&secondNumber=6"
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 42 0 42 0 0 2800 0 --:--:-- --:--:-- --:--:-- 2800{"firstNumber":0,"secondNumber":6,"gcf":6}
Above is another way of sending the curl command, we are finding the GCF of 0 and 6. The GCF is 6. If you don't have curl, you can use the browser or Postman to send your HTTP request.
Spring Boot MockMvc
Now the thing we have been waiting for. Create a package named com.blogspot.jpllosa.controller
under src/test/java/
and create MathFunControllerTest
. This is the class under test. To run the test in STS4, right click, Run As + JUnit test.
package com.blogspot.jpllosa.controller;
import static org.hamcrest.CoreMatchers.is;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
@SpringBootTest
@AutoConfigureMockMvc
public class MathFunControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
public void getGcfTest() throws Exception {
this.mockMvc.perform(get("/get-gcf?firstNumber=12&secondNumber=16"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(jsonPath("$.firstNumber", is(12)))
.andExpect(jsonPath("$.secondNumber", is(16)))
.andExpect(jsonPath("$.gcf", is(4)));
}
}
Now that we have seen the code, let's explain what it does. The @SpringBootTest
annotation tells Spring the following:
- To use the default
ContextLoader
, in this case, it'sSpringBootContextLoader
. - To automatically search for
@SpringBootConfiguration
. - To register a
TestRestTemplate
and/orWebTestClient
. But we won't be using these. - To load a
WebApplicationContext
and provide a mock servlet environment. Used in conjunction with@AutoConfigureMockMvc
forMockMvc
based testing. As the name suggests, Spring will auto-confiureMockMvc
and automatically inject (@Autowired
) it for us.
The @Test
annotation tells JUnit that the method is to be run as a test case. What does this method do? Using MockMvc
, it performs a HTTP GET request on /get-gcf resource with two query parameters. It then prints the response and checks for a 200 status. By using jsonPath
dot notation, we can assert the response body values with the expected values. The dollar sign represents the root member object.
JUnit Passed |
print()
output
2023-01-18 07:52:27.115 INFO 19520 --- [ main] c.b.j.controller.MathFunControllerTest : Started MathFunControllerTest in 2.381 seconds (JVM running for 4.218)
MockHttpServletRequest:
HTTP Method = GET
Request URI = /get-gcf
Parameters = {firstNumber=[12], secondNumber=[16]}
Headers = []
Body = null
Session Attrs = {}
Handler:
Type = com.blogspot.jpllosa.controller.MathFunController
Method = com.blogspot.jpllosa.controller.MathFunController#getGcf(int, int)
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:"application/json"]
Content type = application/json
Body = {"firstNumber":12,"secondNumber":16,"gcf":4}
Forwarded URL = null
Redirected URL = null
Cookies = []
There you have it. A simple example of how to use MockMvc
.