KodaSchool
Learn To Code
Writing Unit Tests for REST APIs with Springboot
Spring Boot provides utilities and annotations to facilitate testing. This can be achieved by using tools like Mockito and JUnit to mock dependencies.
topics
In this tutorial, we are going to implement unit testing in a springboot REST API project. We are going to implement unit testing on an Employee Management System with the basic Create, Read, Update and Delete Functionality.
Unit Testing Introduction
Types of Testing
1) End To End Testing - Testing the entire application, its external dependencies in a production like environment
2) Functional Testing - Testing an application from a users point of view
3) Integration Testing - Ensuring different modules of an application work together correctly.
4) Unit testing - Testing individual components of an application for example methods or classes in isolation. For this tutorial we are going to focus on this.
Building the REST API
Step 1: Setup the Project
To set up the project use a spring initializer. Go to https://start.spring.io.Add the following dependencies to your project: Spring Web, Spring Data JPA, H2 Database, SpringBoot Dev Tools, Springboot Test. Also for this project we are going to use Java 17 , maven and Jar for packaging.
Step 2: Create the Employee Model
Create an Employee Class with the following fields: id, firstName, lastName, Department and email. Here is the Employee Class with the Getters and Setters
@Entity
@Table(name = "employees")
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String firstName;
@Column(nullable = false)
private String lastName;
@Column(nullable = false, unique = true)
private String email;
@Column(nullable = false)
private String department;
// Default constructor
public Employee() {
}
public Employee(long id, String firstName, String lastName, String email, String department) {
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
this.department = department;
}
// getters and setters
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getDepartment() {
return department;
}
public void setDepartment(String department) {
this.department = department;
}
}
Step 3: Create the Employee Respository
@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
Boolean existsByEmail(String email);
}
Step 4: Create the Employee Service
@Service
public class EmployeeService {
@Autowired
private EmployeeRepository employeeRepository;
public Employee createEmployee(Employee employee) {
return employeeRepository.save(employee);
}
public Employee updateEmployee(Long id, Employee updatedEmployee) {
Employee existingEmployee = employeeRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Employee not found"));
existingEmployee.setFirstName(updatedEmployee.getFirstName());
existingEmployee.setLastName(updatedEmployee.getLastName());
existingEmployee.setEmail(updatedEmployee.getEmail());
existingEmployee.setDepartment(updatedEmployee.getDepartment());
return employeeRepository.save(existingEmployee);
}
public void deleteEmployee(Long id) {
Employee employee = employeeRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Employee not found"));
employeeRepository.delete(employee);
}
public List<Employee> getAllEmployees() {
return employeeRepository.findAll();
}
public Employee getEmployeeById(Long id) {
return employeeRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Employee not found"));
}
}
Step 5: Create the Employee Controller
@RestController
@RequestMapping("/api/employees")
public class EmployeeController {
@Autowired
private EmployeeService employeeService;
@PostMapping
public ResponseEntity<Employee> createEmployee(@RequestBody Employee employee) {
Employee createdEmployee = employeeService.createEmployee(employee);
return new ResponseEntity<>(createdEmployee, HttpStatus.CREATED);
}
@PutMapping("/{id}")
public ResponseEntity<Employee> updateEmployee(@PathVariable Long id, @RequestBody Employee employee) {
Employee updatedEmployee = employeeService.updateEmployee(id, employee);
return new ResponseEntity<>(updatedEmployee, HttpStatus.OK);
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteEmployee(@PathVariable Long id) {
employeeService.deleteEmployee(id);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
@GetMapping
public ResponseEntity<List<Employee>> getAllEmployees() {
List<Employee> employees = employeeService.getAllEmployees();
return new ResponseEntity<>(employees, HttpStatus.OK);
}
@GetMapping("/{id}")
public ResponseEntity<Employee> getEmployeeById(@PathVariable Long id) {
Employee employee = employeeService.getEmployeeById(id);
return new ResponseEntity<>(employee, HttpStatus.OK);
}
}
Step 6: Create the ResourceNotFoundException Class
public class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException(String message) {
super(message);
}
}
This is how the folder Structure looks like:
Feel Free to Access the code at this repository: https://github.com/kodaschool/unit-testing-springboot
Testing Dependencies
Each project generated by the springboot has testing in mind. The Package below has all the libraries required to do unit testing. This dependency comes by default when generating a springboot project. Check the pom.xml
file
.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
Inspect the dependencies it comes bundled with by hitting ctrl + Click
on windows or cmd + click
on mac on the artifactId.
Some of the depedencies include:
1) JUnit
- De- facto standard for unit testing in java
2) Spring Test and Springboot Test
- Utilities and integration test support for Springboot applications
3) AssertJ
- An assertion library
4) Hamcrest
- A library of matcher objects
5) Mockito
- A java mocking framework
6) JsonAssert
- An assertion library for JSON
7) JsonPath
- Xpath for JSON
Unit Testing Annotations
@SpringBootTest
- It is used to load the application context and run tests with Spring Boot features.
- It can be used as an alternative to the
@ContextConfiguration
annotation when you need Spring Boot features. - It creates the application context used in tests through
SpringApplication
. - It allows you to test the integration of different components and services.
- It can be configured to load specific classes, set the environment, and set properties.
- It provides support for different web environment modes, including the ability to start a fully running web server
@DataJpaTest
@DataJpaTest
is used to test JPA components in Spring Boot.- It disables full auto-configuration and applies only configuration relevant to JPA tests.
- By default, it uses an embedded in-memory database for testing.
- The application context contains all JPA components, including the in-memory database.
- Each test method runs in its own transaction and is rolled back after execution.
- Ensure you use this annotation when testing the repository layer.
- Useful for testing JPA repositories and custom JPA query methods.
- Scans for
@Entity
classes and configures Spring Data JPA repositories. - Can be combined with
@AutoConfigureTestDatabase
to customize database settings. - SQL queries are logged by default, but this can be disabled.
- Consider using
@SpringBootTest
with@AutoConfigureTestDatabase
for full application configuration with an embedded database. - In JUnit 4, use
@RunWith(SpringRunner.class)
in combination with@DataJpaTest
.
@MockMvc
Allows you to send HTTP requests to your controller and check the response, without actually starting a web server or making network requests. This makes it a fast and efficient way to test your controller logic.
@AutoConfigureMockMvc
Automatically configures MockMvc for you, so you don't have to manually set it up.
@AutoConfigureTestDatabase
Configures a test database for testing database-related functionality.
@BeforeEach
- The
@BeforeEach
annotation is used to mark a method that should be executed before each test method in the test class. - This annotation is typically used to set up the initial state or prepare the environment required for each test method.
- Common tasks performed in a method annotated with
@BeforeEach
include initializing objects, setting up mock objects, or preparing test data. - Methods annotated with
@BeforeEach
are executed before each test method, ensuring that the test environment is consistent and isolated for each test.
@AfterEach
- The
@AfterEach
annotation is used to mark a method that should be executed after each test method in the test class. - This annotation is typically used to clean up resources, reset the state, or perform any necessary teardown tasks after each test method.
- Common tasks performed in a method annotated with
@AfterEach
include releasing resources, closing connections, or resetting objects to their initial state. - Methods annotated with
@AfterEach
are executed after each test method, ensuring that any cleanup or teardown tasks are performed consistently after each test.
@Disabled
Disables a test temporarily.
@RepeatedTest
Repeats a test a specified number of times.
@Sql
Executes sql scripts before and after a test
Unit Testing Best Practices
a) Use Test Driven Development Approach ie write the tests before writing the actual code or write them in parallel.
b) Add Tests To your CI CD pipeline and send notifications in case of failure.
c) Ensure tests work in isolation without interfering with each other
d) Ensure a high test coverage of greater than 80% by using test coverage tools like Sonacube and Jacoco .
e) Use meaningful naming convections for testcases. For example itShouldCreateNewUser.
Unit Testing in Action
Step 1: Testing the Repository Class
- Ensure you have a h2 database whose scope is test that will be used as an in memory database in the pom.xml:
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
- Create a directory inside the test folder and choose resources and configure the h2 database for test inside the application.yml
file like below :
spring:
application:
name: unit-test-demo
datasource:
url: jdbc:h2://mem:db;DB_CLOSE_DELAY=-1
username: sa
password: sa
driver-class-name: org.h2.Driver
Here's what each part of the h2 URL means:
jdbc:h2:
: This is the protocol prefix for connecting to an H2 database using JDBC.//mem:db
: This part indicates that you're connecting to an H2 in-memory database. In-memory databases are created entirely in RAM and are typically used for testing or temporary data storage.DB_CLOSE_DELAY=-1
: This is a parameter that specifies how long the database should remain open after the last connection to it is closed. In this case,-1
means that the database will remain open indefinitely, even after all connections are closed. This is useful to keep the in-memory database alive for the duration of your application's lifecycle
Here is the code after writing the unit test inside the EmployeeRepositoryTest
file inside the test fder.
@DataJpaTest
class EmployeeRepositoryTest {
@Autowired
private EmployeeRepository underTest;
@AfterEach
void tearDown() {
underTest.deleteAll();
}
@Test
void itShouldCheckIfEmployeeExistsByEmail() {
String email = "johndoe@gmail.com";
//given
Employee employee = new Employee(1, "john", "doe", email, "IT");
underTest.save(employee);
//when
boolean expected = underTest.existsByEmail(email);
//then
assertThat(expected).isTrue();
}
@Test
void itShouldCheckIfEmployeeDoesNotExistsByEmail() {
String email = "johndoe@gmail.com";
//given
Employee employee = new Employee(1, "john", "doe", email, "IT");
//when
boolean expected = underTest.existsByEmail(email);
//then
assertThat(expected).isFalse();
}
}
Step 2:
Unit Testing First Steps
We will create unit tests for each layer of the project. These layers are the Service Layer, Controller Layer and Repository Layer. This is best practice that should be followed even in your projects. We are going to use Mockito and WireMock which are within JUnit to create the test cases. We will use mockito to mock the methods.
share
Backend and DevOps Engineer