What is Integration Testing?
Integration testing involves testing multiple components of an application to ensure that they work together correctly. In the context of a Spring Boot application, integration testing often includes verifying the interaction between the repository layer and the actual database. This ensures that the application correctly handles data persistence, retrieval, and manipulation.
Why Use MySQL for Integration Testing?
While unit tests often use in-memory databases like H2 for faster execution, integration testing benefits from using the actual database used in production, such as MySQL. This helps in:
- Simulating real-world scenarios: Ensuring the queries, configurations, and behavior align with the production environment.
- Testing schema compatibility: Verifying that schema generation and migrations work as expected in MySQL.
- Catching environment-specific issues: Identifying problems that might arise from database-specific behaviors, like indexing and performance.
Step 1: Add Maven Dependencies
To set up Spring Data JPA with MySQL and Lombok for reducing boilerplate code, add the following dependencies to your pom.xml
:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>9.0.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
spring-boot-starter-data-jpa
: Provides Spring Data JPA and Hibernate for database operations.mysql-connector-j
: The MySQL JDBC driver is required to connect the application to the MySQL database.lombok
: Reduces boilerplate code by auto-generating setters, getters, and constructors.spring-boot-starter-test
: Provides JUnit and other testing tools.
Step 2: Project Structure
The following diagram shows the structure of your project:
src/main/java
│
├── net
│ └── javaguides
│ └── springboot
│ ├── model
│ │ └── Employee.java
│ └── repository
│ └── EmployeeRepository.java
│
src/test/java
│
├── net
│ └── javaguides
│ └── springboot
│ └── repository
│ └── EmployeeRepositoryTests.java
Step 3: Configure MySQL Database
You need to configure the application.properties
file to connect to a MySQL database. Add the following properties:
spring.jpa.show-sql=true
spring.datasource.url=jdbc:mysql://localhost:3306/ems
spring.datasource.username=root
spring.datasource.password=Mysql@123
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
: Displays the SQL queries being executed.spring.datasource.url
: The URL of the MySQL database.spring.datasource.username
andspring.datasource.password
: Credentials to access the MySQL database.spring.jpa.hibernate.ddl-auto=update
: Automatically updates the database schema based on entity definitions.
Step 4: Create the Employee
Entity
We’ll create the Employee
entity that maps to the employees
table in the MySQL database:
import lombok.*;
import jakarta.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;
}
Explanation:
@Entity
: Marks the class as a JPA entity that maps to theemployees
table.@Table(name = "employees")
: Specifies the table name in the database.- Lombok Annotations:
@Setter
,@Getter
,@AllArgsConstructor
,@NoArgsConstructor
, and@Builder
automatically generate code like setters, getters, constructors, and a builder pattern.
Step 5: Create the Repository Layer
The repository interface EmployeeRepository
will extend JpaRepository
to provide basic CRUD operations. We will also define custom queries using JPQL and native SQL.
import net.javaguides.springboot.model.Employee;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.Optional;
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
Optional<Employee> findByEmail(String email);
// Custom JPQL query with index parameters
@Query("select e from Employee e where e.firstName = ?1 and e.lastName = ?2")
Employee findByJPQL(String firstName, String lastName);
// Custom JPQL query with named parameters
@Query("select e from Employee e where e.firstName =:firstName and e.lastName =:lastName")
Employee findByJPQLNamedParams(@Param("firstName") String firstName, @Param("lastName") String lastName);
// Custom native SQL query with index parameters
@Query(value = "select * from employees e where e.first_name =?1 and e.last_name =?2", nativeQuery = true)
Employee findByNativeSQL(String firstName, String lastName);
// Custom native SQL query with named parameters
@Query(value = "select * from employees e where e.first_name =:firstName and e.last_name =:lastName", nativeQuery = true)
Employee findByNativeSQLNamed(@Param("firstName") String firstName, @Param("lastName") String lastName);
}
Explanation of Repository Methods:
findByEmail(String email)
: Finds an employee by their email address.findByJPQL(String firstName, String lastName)
: A custom JPQL query using indexed parameters to find an employee based on first and last name.findByJPQLNamedParams(String firstName, String lastName)
: A custom JPQL query using named parameters for readability.findByNativeSQL(String firstName, String lastName)
: A custom native SQL query using indexed parameters.findByNativeSQLNamed(String firstName, String lastName)
: A custom native SQL query using named parameters.
findByEmail(String email)
: Finds an employee by their email address.findByJPQL(String firstName, String lastName)
: A custom JPQL query using indexed parameters to find an employee based on first and last name.findByJPQLNamedParams(String firstName, String lastName)
: A custom JPQL query using named parameters for readability.findByNativeSQL(String firstName, String lastName)
: A custom native SQL query using indexed parameters.findByNativeSQLNamed(String firstName, String lastName)
: A custom native SQL query using named parameters.Step 6: Testing the Repository Layer - EmployeeRepositoryTests
Now that we have set up the repository and entity, we can move on to writing integration tests for the repository layer. The EmployeeRepositoryTests
class will test various repository methods, including custom queries.
package net.javaguides.springboot.repository;
import net.javaguides.springboot.model.Employee;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import java.util.List;
import java.util.Optional;
@DataJpaTest
public class EmployeeRepositoryTests {
@Autowired
private EmployeeRepository employeeRepository;
private Employee employee;
@BeforeEach
public void setup(){
employee = Employee.builder()
.firstName("Ramesh")
.lastName("Fadatare")
.email("ramesh@gmail.com")
.build();
}
// JUnit test for save employee operation
@Test
public void givenEmployeeObject_whenSave_thenReturnSavedEmployee(){
// given - precondition or setup
Employee employee = Employee.builder()
.firstName("Ramesh")
.lastName("Ramesh")
.email("ramesh@gmail.com")
.build();
// when - action or the behaviour that we are going test
Employee savedEmployee = employeeRepository.save(employee);
// then - verify the output
assertThat(savedEmployee).isNotNull();
assertThat(savedEmployee.getId()).isGreaterThan(0);
}
// JUnit test for get all employees operation
@DisplayName("JUnit test for get all employees operation")
@Test
public void givenEmployeesList_whenFindAll_thenEmployeesList(){
// given - precondition or setup
Employee employee1 = Employee.builder()
.firstName("John")
.lastName("Cena")
.email("cena@gmail.com")
.build();
employeeRepository.save(employee);
employeeRepository.save(employee1);
// when - action or the behaviour that we are going test
List<Employee> employeeList = employeeRepository.findAll();
// then - verify the output
assertThat(employeeList).isNotNull();
assertThat(employeeList.size()).isEqualTo(2);
}
// JUnit test for get employee by id operation
@DisplayName("JUnit test for get employee by id operation")
@Test
public void givenEmployeeObject_whenFindById_thenReturnEmployeeObject(){
// given - precondition or setup
employeeRepository.save(employee);
// when - action or the behaviour that we are going test
Employee employeeDB = employeeRepository.findById(employee.getId()).get();
// then - verify the output
assertThat(employeeDB).isNotNull();
}
// JUnit test for get employee by email operation
@DisplayName("JUnit test for get employee by email operation")
@Test
public void givenEmployeeEmail_whenFindByEmail_thenReturnEmployeeObject(){
// given - precondition or setup
employeeRepository.save(employee);
// when - action or the behaviour that we are going test
Employee employeeDB = employeeRepository.findByEmail(employee.getEmail()).get();
// then - verify the output
assertThat(employeeDB).isNotNull();
}
// JUnit test for update employee operation
@DisplayName("JUnit test for update employee operation")
@Test
public void givenEmployeeObject_whenUpdateEmployee_thenReturnUpdatedEmployee(){
// given - precondition or setup
employeeRepository.save(employee);
// when - action or the behaviour that we are going test
Employee savedEmployee = employeeRepository.findById(employee.getId()).get();
savedEmployee.setEmail("ram@gmail.com");
savedEmployee.setFirstName("Ram");
Employee updatedEmployee = employeeRepository.save(savedEmployee);
// then - verify the output
assertThat(updatedEmployee.getEmail()).isEqualTo("ram@gmail.com");
assertThat(updatedEmployee.getFirstName()).isEqualTo("Ram");
}
// JUnit test for delete employee operation
@DisplayName("JUnit test for delete employee operation")
@Test
public void givenEmployeeObject_whenDelete_thenRemoveEmployee(){
// given - precondition or setup
employeeRepository.save(employee);
// when - action or the behaviour that we are going test
employeeRepository.deleteById(employee.getId());
Optional<Employee> employeeOptional = employeeRepository.findById(employee.getId());
// then - verify the output
assertThat(employeeOptional).isEmpty();
}
// JUnit test for custom query using JPQL with index
@DisplayName("JUnit test for custom query using JPQL with index")
@Test
public void givenFirstNameAndLastName_whenFindByJPQL_thenReturnEmployeeObject(){
// given - precondition or setup
employeeRepository.save(employee);
String firstName = "Ramesh";
String lastName = "Fadatare";
// when - action or the behaviour that we are going test
Employee savedEmployee = employeeRepository.findByJPQL(firstName, lastName);
// then - verify the output
assertThat(savedEmployee).isNotNull();
}
// JUnit test for custom query using JPQL with Named params
@DisplayName("JUnit test for custom query using JPQL with Named params")
@Test
public void givenFirstNameAndLastName_whenFindByJPQLNamedParams_thenReturnEmployeeObject(){
// given - precondition or setup
employeeRepository.save(employee);
String firstName = "Ramesh";
String lastName = "Fadatare";
// when - action or the behaviour that we are going test
Employee savedEmployee = employeeRepository.findByJPQLNamedParams(firstName, lastName);
// then - verify the output
assertThat(savedEmployee).isNotNull();
}
// JUnit test for custom query using native SQL with index
@DisplayName("JUnit test for custom query using native SQL with index")
@Test
public void givenFirstNameAndLastName_whenFindByNativeSQL_thenReturnEmployeeObject(){
// given - precondition or setup
employeeRepository.save(employee);
// when - action or the behaviour that we are going test
Employee savedEmployee = employeeRepository.findByNativeSQL(employee.getFirstName(), employee.getLastName());
// then - verify the output
assertThat(savedEmployee).isNotNull();
}
// JUnit test for custom query using native SQL with named params
@DisplayName("JUnit test for custom query using native SQL with named params")
@Test
public void givenFirstNameAndLastName_whenFindByNativeSQLNamedParams_thenReturnEmployeeObject(){
// given - precondition or setup
employeeRepository.save(employee);
// when - action or the behaviour that we are going test
Employee savedEmployee = employeeRepository.findByNativeSQLNamed(employee.getFirstName(), employee.getLastName());
// then - verify the output
assertThat(savedEmployee).isNotNull();
}
}
Explanation of JUnit Test Methods:
givenEmployeeObject_whenSave_thenReturnSavedEmployee()
: Tests if anEmployee
object is saved correctly using thesave()
method.givenEmployeesList_whenFindAll_thenEmployeesList()
: Verifies that thefindAll()
method returns a list of employees.givenEmployeeObject_whenFindById_thenReturnEmployeeObject()
: Tests if the repository can retrieve an employee byid
usingfindById()
.givenEmployeeEmail_whenFindByEmail_thenReturnEmployeeObject()
: Verifies that the repository can retrieve an employee byemail
usingfindByEmail()
.givenEmployeeObject_whenUpdateEmployee_thenReturnUpdatedEmployee()
: Tests if anEmployee
object is updated correctly.givenEmployeeObject_whenDelete_thenRemoveEmployee()
: Tests if anEmployee
object is deleted from the database.- Custom Query Tests (
findByJPQL()
,findByJPQLNamedParams()
, etc.): These tests validate that custom JPQL and native SQL queries return the correct employee records.
Conclusion
In this tutorial, we explored how to write integration tests for the repository layer in a Spring Boot application using JUnit 5 and MySQL. We covered basic CRUD operations and custom queries and ensured the repository interacts correctly with the MySQL database.
Comments
Post a Comment
Leave Comment