→ How to Build Pagination and Sorting with Spring Boot and JPA
In which, I showed you how to code pagination and sorting with spring boot and JPA. That part 1 was more focused on production code, and this one will show you how to test that code.
So, in this article, you will learn how to write unit and integration tests for Pagination and Sorting using JPA/ Hibernate with SQL (Postgres) Database and Spring Boot.
Prerequisite
In order to follow this tutorial, you will need:
- Java 8 knowledge
- Docker installed on your computer
- Installed and configured Postgres Database
Configuring Testing Environment
In order to write and perform unit and integration testing for your application, you will to create a file application-test.yml
in your test resources folder. And add the following content in the YAML file.
Once the test has been configured via the YAML file, now we can start writing our unit test.
Unit Testing
To perform unit testing of our previous tutorial, we should test the business logic of our service layer.
To do so, we will use Mockito Framework
in our test class. Mockito is used to manage dependencies and mocking external interactions.
import com.example.demo.repository.StudentRepository;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class StudentServiceTest {
@InjectMocks
private StudentService service;
@Mock
private StudentRepository repository;
@Test
@DisplayName("Should fetch all the students in the database")
void shouldFetchAllTheStudentInDatabase() {
// Arrange
var students = mock(Page.class);
when(repository.findAll(any(PageRequest.class))).thenReturn(students);
// Act
service.findAllStudents(PageRequest.of(1, 10, Sort.Direction.ASC, "name"));
// Assert
verify(repository).findAll(any(PageRequest.class));
verifyNoMoreInteractions(repository);
}
}
If you wonder why we are using @Mock
and @InjectMocks
, here is a simple explanation – @Mock creates a mock. @InjectMocks creates an instance of the class and injects the mocks that are created with the @Mock (or @Spy) annotations into this instance.
Note, you must use @ExtendWith(MockitoExtension.class)
to initialize these mock and inject them (JUnit 5).
With JUnit 4, you must use @RunWith(MockitoJUnitRunner.class) or Mockito.initMocks(this).
Integration Testing
Integration testing is a type of test which involves integrating the various modules of your application and then testing their behaviour as a combined, or integrated | unit.
Verifying if all the individual units are communicating with each other properly and working as intended post-integration is essential for every production grade application.
In our case, we write the integration test to confirm that the endpoints exposed in our Rest API can perform operations on our database, which is 99.9% identical to the production one because we use Testcontainers to match the same database container configuration used for the production database.
So, the first part is to create an abstract class TestContainers
which will be used as a parent class for our integration tests class.
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.utility.DockerImageName;
@Testcontainers(disabledWithoutDocker = true)
public class TestContainers {
@Container
public static PostgreSQLContainer<?> postgreSQLContainer = new PostgreSQLContainer<>(
DockerImageName.parse("postgres:15.1-alpine"))
.withUsername("1kevinson")
.withPassword("admin");
static {
postgreSQLContainer.start();
}
@DynamicPropertySource
static void databaseProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgreSQLContainer::getJdbcUrl);
registry.add("spring.datasource.username", postgreSQLContainer::getUsername);
registry.add("spring.datasource.password", postgreSQLContainer::getPassword);
}
}
Then use this class as a parent class for our integration test class.
To do so, we will use MockMvc
, which is defined by the official documentation as The Spring MVC Test framework, that provides support for testing Spring MVC applications. It performs full Spring MVC request handling but via mock request and response objects instead of a running server.
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.annotation.DirtiesContext;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;
import static org.hamcrest.Matchers.hasSize;
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;
@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("test")
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
public class StudentControllerTest extends TestContainers {
@Autowired
private MockMvc mockMvc;
@Test
@DisplayName("Should retrieve all students with default parameters")
void shouldRetrieveAllStudentsWithDefaultParameters() throws Exception {
mockMvc.perform(get("/students"))
.andDo(print())
.andExpect(status().isPartialContent())
.andExpect(jsonPath("$").isNotEmpty())
.andExpect(jsonPath("$.students", hasSize(10)))
.andExpect(jsonPath("$.pageNumber").value(1))
.andExpect(jsonPath("$.totalElements").value(45))
.andExpect(jsonPath("$.totalPages").value(5))
.andExpect(jsonPath("$.lastPage").value(Boolean.FALSE));
}
@Test
@DisplayName("Should retrieve all students with custom parameters")
void shouldRetrieveAllStudentsWithCustomParameters() throws Exception {
mockMvc.perform(get("/students")
.param("pageNumber","9")
.param("size","5")
.param("sort","name")
.param("direction","ASC"))
.andDo(print())
.andExpect(status().isPartialContent())
.andExpect(jsonPath("$").isNotEmpty())
.andExpect(jsonPath("$.students", hasSize(5)))
.andExpect(jsonPath("$.pageNumber").value(9))
.andExpect(jsonPath("$.totalElements").value(45))
.andExpect(jsonPath("$.totalPages").value(9))
.andExpect(jsonPath("$.lastPage").value(Boolean.TRUE));
}
}
The code source of these 2 parts :
- Part 1 → How to Build Pagination and Sorting with Spring Boot and JPA
- Part 2 → Unit and Integration Testing Pagination and Sorting With JPA, JUnit and Testcontainers
It can be found in my GitHub here.
I hope you enjoyed reading this, and I'm curious to hear if this tutorial helped you. Please let me know your thoughts below in the comments. Don't forget to subscribe to my newsletter to avoid missing my upcoming blog posts.
You can also find me here LinkedIn • Twitter • GitHub or Medium