Learn to write integration tests for a given REST controller using @SpringBootTest and Junit 5. This technique can be applied to spring boot as well as spring MVC applications.
1. What to Test in Integration Testing?
While doing integration testing in spring boot applications, we shall keep in mind that:
- An integration test is supposed to test whether different modules are bounded correctly and if they work as expected.
- The integration tests shall not utilize the actual production dependencies (e.g., database/network), and they can mimic certain behaviors.
- The application shall run in ApplicationContext and run tests in it.
- Spring boot provides @SpringBootTest annotation, which starts the embedded server, creates a web environment and then enables @Test methods to do integration testing. Use its
webEnvironment
attribute for it. It also creates theApplicationContext
used in the tests. - Using an in-memory database is recommended for mimicking the database. Though it is not mandatory, we can use mockito to mock the database interactions.
- It is recommended to use test-specific configurations using @TestConfiguration annotation.
TIP: Spring boot provides lots of specialized annotations to test certain part of applications e.g.
@WebMvcTest
to test web layer or@DataJpaTest
to test persistence later.Use
@SpringBootTest
for tests that cover the whole Spring Boot application from incoming request to database.
2. Writing the Integration Tests
2.1. Maven
Start by including the required dependencies. We need to use spring-boot-starter-test, which will internally use spring-test and other dependent libraries.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
2.2. REST Controller to Test
Here is the Spring boot rest controller we will be writing tests for.
- The controller has dependency on EmployeeRepository class for persistence.
- getEmployees() method returns list of all employees. Typically, in real applications, it will accept pagination parameters.
- addEmployee() api need access to the request context using
ServletUriComponentsBuilder
. - addEmployee() api returns HTTP status and header using
ResponseEntity
class.
import java.net.URI;
import java.util.ArrayList;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import com.howtodoinjava.rest.dao.EmployeeRepository;
import com.howtodoinjava.rest.model.Employee;
import com.howtodoinjava.rest.model.Employees;
@RestController
public class EmployeeController
{
@Autowired
private EmployeeRepository employeeRepository;
@GetMapping(path="/employees", produces = "application/json")
public Employees getEmployees()
{
Employees response = new Employees();
ArrayList<Employee> list = new ArrayList<>();
employeeRepository.findAll().forEach(e -> list.add(e));
response.setEmployeeList(list);
return response;
}
@PostMapping(path= "/employees", consumes = "application/json", produces = "application/json")
public ResponseEntity<Object> addEmployee(@RequestBody Employee employee) {
//add resource
employee = employeeRepository.save(employee);
//Create resource location
URI location = ServletUriComponentsBuilder.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(employee.getId())
.toUri();
//Send location in response
return ResponseEntity.created(location).build();
}
}
2.3. Integration Tests Example
The test class given below contains integration tests for the spring boot rest controller mentioned above. This test class:
- uses @SpringBootTest annotation, which loads the actual application context.
- uses WebEnvironment.RANDOM_PORT to create run the application at some random server port.
- @LocalServerPort gets the reference of port where the server has started. It helps in building the actual request URIs to mimic real client interactions.
- Use TestRestTemplate class helps in invoking the HTTP requests, which are handled by the controller class.
- @Sql annotation helps in populating the database with some prerequisite data if the test is dependent on it to test the behavior correctly.
org.junit.jupiter.api.Test
annotations are from Junit 5 and mark the method as a test method to run.
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.jdbc.Sql;
import com.howtodoinjava.rest.model.Employee;
import com.howtodoinjava.rest.model.Employees;
@SpringBootTest(classes = SpringBootDemoApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT)
public class EmployeeControllerIntegrationTests
{
@LocalServerPort
private int port;
@Autowired
private TestRestTemplate restTemplate;
@Sql({ "schema.sql", "data.sql" })
@Test
public void testAllEmployees()
{
assertTrue(
this.restTemplate
.getForObject("http://localhost:" + port + "/employees", Employees.class)
.getEmployeeList().size() == 3);
}
@Test
public void testAddEmployee() {
Employee employee = new Employee("Lokesh", "Gupta", "howtodoinjava@gmail.com");
ResponseEntity<String> responseEntity = this.restTemplate
.postForEntity("http://localhost:" + port + "/employees", employee, String.class);
assertEquals(201, responseEntity.getStatusCodeValue());
}
}
Run the above tests within IDE. I have used Eclipse. The test class starts the whole application in an embedded server and executes each test one by one.
3. Conclusion
In this spring boot integration testing example with Junit 5, we learned to write tests that test multiple layers of applications in a single test. They verify whether the controller and persistence layers work together correctly or not.
We are not required to use an actual webserver to run the application during integration testing, but we can definitely make use of embedded servers in Spring boot.
Happy Learning !!
Leave a Reply