In this tutorial, we will learn how to perform Integration testing Spring boot application using @SpringBootTest
annotation.
We use MySQL database to store and retrieve the data.
Let’s first take a look at the overview of @SpringBootTest
annotation.
@SpringBootTest Annotation
Spring Boot provides @SpringBootTest
annotation for Integration testing. This annotation creates an application context and loads the full application context.
@SpringBootTest
will bootstrap the full application context, which means we can @Autowire
any bean that’s picked up by component scanning into our test.
It starts the embedded server, creates a web environment, and then enables @Test
methods to do integration testing.
By default, @SpringBootTest
does not start a server. We need to add the attribute webEnvironment to further refine how your tests run. It has several options:
- MOCK(Default): Loads a web ApplicationContext and provides a mock web environment.
- RANDOM_PORT: Loads a WebServerApplicationContext and provides a real web environment. The embedded server is started and listened to a random port. This is the one that should be used for the integration test.
- DEFINED_PORT: Loads a WebServerApplicationContext and provides a real web environment. NONE: Loads an ApplicationContext by using SpringApplication but does not provide any web environment.
- Java 11+
- Spring Boot
- Spring Data JPA
- MySQL
- Lombok
- JUnit 5 Framework
- IntelliJ IDEA
- Docker
- Maven
What is Integration Testing
As the name suggests, integration tests focus on integrating different layers of the application. That also means no mocking is involved.
Basically, we write integration tests for testing a feature that may involve interaction with multiple components.
Examples: Integration testing of complete Employee Management Feature ( EmployeeRepository, EmployeeService, EmployeeController).
Integration testing of complete User Management Feature (UserController, UserService, and UserRepository).
Integration testing of complete Login Feature (LoginRespository, LoginController, Login Service), etc
1. Create Spring Boot Application
Using spring initialize, create a Spring Boot project and add the following dependencies:
- Spring Web
- Spring Data JPA
- Lombok
- MySQL Driver
Generate the Spring boot project as a zip file, extract it, and import it into IntelliJ IDEA.
Let’s use the MySQL database to store and retrieve the data in this example and we gonna use Hibernate properties to create and drop tables.
Open the application.properties file and add the following configuration to it:
1
2
3
4
5
6
7
|
spring.datasource.url=jdbc:mysql://localhost:3306/demo?useSSL=false
spring.datasource.username=root
spring.datasource.password=Mysql@123
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.hibernate.ddl-auto = create-drop
|
Make sure that you will create a demo database before running the Spring boot application.
Also, change the MySQL username and password as per your MySQL installation on your machine.
3. Create JPA Entity
Next, let’s create an Employee JPA entity with the following content:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
import lombok.*;
import javax.persistence.*;
@Setter
@Getter
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Entity
@Table(name = "employees")
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
@Column(name = "first_name", nullable = false)
private String firstName;
@Column(name = "last_name", nullable = false)
private String lastName;
@Column(nullable = false)
private String email;
}
|
Note that we are using Lombok annotations to reduce the boilerplate code.
@Entity
annotation is used to mark the class as a persistent Java class.
@Table
annotation is used to provide the details of the table that this entity will be mapped to.
@Id
annotation is used to define the primary key.
@GeneratedValue
annotation is used to define the primary key generation strategy. In the above case, we have declared the primary key to be an Auto Increment field.
@Column
annotation is used to define the properties of the column that will be mapped to the annotated field. You can define several properties like name, length, nullable, updateable, etc.
4. Create Repository Layer
Let’s create EmployeeRepository which extends the JpaRepository interface:
1
2
3
4
5
6
|
import net.javaguides.springboot.model.Employee;
import org.springframework.data.jpa.repository.JpaRepository;
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
}
|
5. Create Service Layer
EmployeeService
Let’s create an EmployeeService
interface with CRUD methods:
1
2
3
4
5
6
7
8
9
10
11
12
|
import net.javaguides.springboot.model.Employee;
import java.util.List;
import java.util.Optional;
public interface EmployeeService {
Employee saveEmployee(Employee employee);
List<Employee> getAllEmployees();
Optional<Employee> getEmployeeById(long id);
Employee updateEmployee(Employee updatedEmployee);
void deleteEmployee(long id);
}
|
EmployeeServiceImpl
Let’s create an EmployeeServiceImpl
class that implements the EmployeeService
interface:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
import net.javaguides.springboot.exception.ResourceNotFoundException;
import net.javaguides.springboot.model.Employee;
import net.javaguides.springboot.repository.EmployeeRepository;
import net.javaguides.springboot.service.EmployeeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
@Service
public class EmployeeServiceImpl implements EmployeeService {
private EmployeeRepository employeeRepository;
public EmployeeServiceImpl(EmployeeRepository employeeRepository) {
this.employeeRepository = employeeRepository;
}
@Override
public Employee saveEmployee(Employee employee) {
Optional<Employee> savedEmployee = employeeRepository.findByEmail(employee.getEmail());
if(savedEmployee.isPresent()){
throw new ResourceNotFoundException("Employee already exist with given email:" + employee.getEmail());
}
return employeeRepository.save(employee);
}
@Override
public List<Employee> getAllEmployees() {
return employeeRepository.findAll();
}
@Override
public Optional<Employee> getEmployeeById(long id) {
return employeeRepository.findById(id);
}
@Override
public Employee updateEmployee(Employee updatedEmployee) {
return employeeRepository.save(updatedEmployee);
}
@Override
public void deleteEmployee(long id) {
employeeRepository.deleteById(id);
}
}
|
6. Controller Layer
Let’s create CRUD REST APIs for creating, retrieving, updating, and deleting an Employee:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
import net.javaguides.springboot.model.Employee;
import net.javaguides.springboot.service.EmployeeService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/employees")
public class EmployeeController {
private EmployeeService employeeService;
public EmployeeController(EmployeeService employeeService) {
this.employeeService = employeeService;
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public Employee createEmployee(@RequestBody Employee employee){
return employeeService.saveEmployee(employee);
}
@GetMapping
public List<Employee> getAllEmployees(){
return employeeService.getAllEmployees();
}
@GetMapping("{id}")
public ResponseEntity<Employee> getEmployeeById(@PathVariable("id") long employeeId){
return employeeService.getEmployeeById(employeeId)
.map(ResponseEntity::ok)
.orElseGet(() -> ResponseEntity.notFound().build());
}
@PutMapping("{id}")
public ResponseEntity<Employee> updateEmployee(@PathVariable("id") long employeeId,
@RequestBody Employee employee){
return employeeService.getEmployeeById(employeeId)
.map(savedEmployee -> {
savedEmployee.setFirstName(employee.getFirstName());
savedEmployee.setLastName(employee.getLastName());
savedEmployee.setEmail(employee.getEmail());
Employee updatedEmployee = employeeService.updateEmployee(savedEmployee);
return new ResponseEntity<>(updatedEmployee, HttpStatus.OK);
})
.orElseGet(() -> ResponseEntity.notFound().build());
}
@DeleteMapping("{id}")
public ResponseEntity<String> deleteEmployee(@PathVariable("id") long employeeId){
employeeService.deleteEmployee(employeeId);
return new ResponseEntity<String>("Employee deleted successfully!.", HttpStatus.OK);
}
}
|
7. Writing Integration Tests for CRUD REST API’s
Now, let’s create Integration JUnit tests for CRUD REST APIs. We gonna use the @SpringBootTest
annotation to run Integration tests with respect to the MySQL database.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
|
import com.fasterxml.jackson.databind.ObjectMapper;
import net.javaguides.springboot.model.Employee;
import net.javaguides.springboot.repository.EmployeeRepository;
import org.junit.jupiter.api.BeforeEach;
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.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import static org.hamcrest.CoreMatchers.is;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.willDoNothing;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
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;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
public class EmployeeControllerITests {
@Autowired
private MockMvc mockMvc;
@Autowired
private EmployeeRepository employeeRepository;
@Autowired
private ObjectMapper objectMapper;
@BeforeEach
void setup(){
employeeRepository.deleteAll();
}
@Test
public void givenEmployeeObject_whenCreateEmployee_thenReturnSavedEmployee() throws Exception{
// given - precondition or setup
Employee employee = Employee.builder()
.firstName("Ramesh")
.lastName("Fadatare")
.email("ramesh@gmail.com")
.build();
// when - action or behaviour that we are going test
ResultActions response = mockMvc.perform(post("/api/employees")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(employee)));
// then - verify the result or output using assert statements
response.andDo(print()).
andExpect(status().isCreated())
.andExpect(jsonPath("$.firstName",
is(employee.getFirstName())))
.andExpect(jsonPath("$.lastName",
is(employee.getLastName())))
.andExpect(jsonPath("$.email",
is(employee.getEmail())));
}
// JUnit test for Get All employees REST API
@Test
public void givenListOfEmployees_whenGetAllEmployees_thenReturnEmployeesList() throws Exception{
// given - precondition or setup
List<Employee> listOfEmployees = new ArrayList<>();
listOfEmployees.add(Employee.builder().firstName("Ramesh").lastName("Fadatare").email("ramesh@gmail.com").build());
listOfEmployees.add(Employee.builder().firstName("Tony").lastName("Stark").email("tony@gmail.com").build());
employeeRepository.saveAll(listOfEmployees);
// when - action or the behaviour that we are going test
ResultActions response = mockMvc.perform(get("/api/employees"));
// then - verify the output
response.andExpect(status().isOk())
.andDo(print())
.andExpect(jsonPath("$.size()",
is(listOfEmployees.size())));
}
// positive scenario - valid employee id
// JUnit test for GET employee by id REST API
@Test
public void givenEmployeeId_whenGetEmployeeById_thenReturnEmployeeObject() throws Exception{
// given - precondition or setup
Employee employee = Employee.builder()
.firstName("Ramesh")
.lastName("Fadatare")
.email("ramesh@gmail.com")
.build();
employeeRepository.save(employee);
// when - action or the behaviour that we are going test
ResultActions response = mockMvc.perform(get("/api/employees/{id}", employee.getId()));
// then - verify the output
response.andExpect(status().isOk())
.andDo(print())
.andExpect(jsonPath("$.firstName", is(employee.getFirstName())))
.andExpect(jsonPath("$.lastName", is(employee.getLastName())))
.andExpect(jsonPath("$.email", is(employee.getEmail())));
}
// negative scenario - valid employee id
// JUnit test for GET employee by id REST API
@Test
public void givenInvalidEmployeeId_whenGetEmployeeById_thenReturnEmpty() throws Exception{
// given - precondition or setup
long employeeId = 1L;
Employee employee = Employee.builder()
.firstName("Ramesh")
.lastName("Fadatare")
.email("ramesh@gmail.com")
.build();
employeeRepository.save(employee);
// when - action or the behaviour that we are going test
ResultActions response = mockMvc.perform(get("/api/employees/{id}", employeeId));
// then - verify the output
response.andExpect(status().isNotFound())
.andDo(print());
}
// JUnit test for update employee REST API - positive scenario
@Test
public void givenUpdatedEmployee_whenUpdateEmployee_thenReturnUpdateEmployeeObject() throws Exception{
// given - precondition or setup
Employee savedEmployee = Employee.builder()
.firstName("Ramesh")
.lastName("Fadatare")
.email("ramesh@gmail.com")
.build();
employeeRepository.save(savedEmployee);
Employee updatedEmployee = Employee.builder()
.firstName("Ram")
.lastName("Jadhav")
.email("ram@gmail.com")
.build();
// when - action or the behaviour that we are going test
ResultActions response = mockMvc.perform(put("/api/employees/{id}", savedEmployee.getId())
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(updatedEmployee)));
// then - verify the output
response.andExpect(status().isOk())
.andDo(print())
.andExpect(jsonPath("$.firstName", is(updatedEmployee.getFirstName())))
.andExpect(jsonPath("$.lastName", is(updatedEmployee.getLastName())))
.andExpect(jsonPath("$.email", is(updatedEmployee.getEmail())));
}
// JUnit test for update employee REST API - negative scenario
@Test
public void givenUpdatedEmployee_whenUpdateEmployee_thenReturn404() throws Exception{
// given - precondition or setup
long employeeId = 1L;
Employee savedEmployee = Employee.builder()
.firstName("Ramesh")
.lastName("Fadatare")
.email("ramesh@gmail.com")
.build();
employeeRepository.save(savedEmployee);
Employee updatedEmployee = Employee.builder()
.firstName("Ram")
.lastName("Jadhav")
.email("ram@gmail.com")
.build();
// when - action or the behaviour that we are going test
ResultActions response = mockMvc.perform(put("/api/employees/{id}", employeeId)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(updatedEmployee)));
// then - verify the output
response.andExpect(status().isNotFound())
.andDo(print());
}
// JUnit test for delete employee REST API
@Test
public void givenEmployeeId_whenDeleteEmployee_thenReturn200() throws Exception{
// given - precondition or setup
Employee savedEmployee = Employee.builder()
.firstName("Ramesh")
.lastName("Fadatare")
.email("ramesh@gmail.com")
.build();
employeeRepository.save(savedEmployee);
// when - action or the behaviour that we are going test
ResultActions response = mockMvc.perform(delete("/api/employees/{id}", savedEmployee.getId()));
// then - verify the output
response.andExpect(status().isOk())
.andDo(print());
}
}
|
8. Demo
Here is the output of the above Integration test cases:
9. Conclusion
In this tutorial, we have discussed how to perform Spring Boot Integration testing with MySQL using @SpringBootTest annotation.
Reference https://www.javaguides.net/2022/03/spring-boot-integration-testing-mysql-crud-rest-api-tutorial.html