Spring Boot REST API Integration Testing using JUnit, Mockito and Testcontainers

In this tutorial, we will learn how to do Spring Boot application Integration Testing using Testcontainers.

Check out my Spring boot testing Udemy course: Testing Spring Boot Application with JUnit and Mockito (Includes Testcontainers)

First, we write Integration tests using a local MySQL database, and then we will address the problem with Testcontainers as a solution.

SpringBoot provides excellent support for integration testing using @SpringBootTest annotation. We can use @SpringBootTest annotation to load the application context and test various components. 

@SpringBootTest will bootstrap the full application context, which means we can @Autowire any bean that's picked up by component scanning into our test.

In this tutorial, we are going to use @SpringBootTest annotation for Integration testing.

YouTube Video

Tools and technologies used

  • Java 11+
  • Spring Boot
  • Spring Data JPA
  • MySQL
  • Lombok
  • JUnit 5 Framework
  • IntelliJ IDEA
  • Testcontainers
  • 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

Development Steps

  1. Create Spring Boot Application
  2. Configure MySQL database
  3. Create JPA Entity
  4. Create Spring Data JPA Repository
  5. Create Spring Boot REST Controller
  6. Create Integration Tests with MySQL database
  7. What Testcontainers
  8. Adding Testcontainers to Spring Boot Project
  9. Write Integration Tests using Testcontainers
  10. Demo

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.

2. Configure MySQL database

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:
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 a Student JPA entity: 
package net.javaguides.spirngboot.entity;

import lombok.*;

import javax.persistence.*;

@Setter
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "students")
public class Student {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    @Column(name = "first_name")
    private String firstName;

    @Column(name = "last_name")
    private String lastName;
    private String email;
}

4. Create Spring Data JPA Repository

Let's create StudentRepository which extends the JpaRepository interface:
package net.javaguides.spirngboot.repository;

import net.javaguides.spirngboot.entity.Student;
import org.springframework.data.jpa.repository.JpaRepository;

public interface StudentRepository extends JpaRepository<Student, Long> {
}

5. Create Spring Boot REST Controller

Let's create StudentController class and add these couple of REST endpoints:
package net.javaguides.spirngboot.controller;

import net.javaguides.spirngboot.entity.Student;
import net.javaguides.spirngboot.repository.StudentRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/students")
public class StudentController {

    @Autowired
    private StudentRepository studentRepository;

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public Student createStudent(@RequestBody Student student){
        return studentRepository.save(student);
    }

    @GetMapping
    public List<Student> getAllStudents(){
        return studentRepository.findAll();
    }
}

6. Create Integration Tests with MySQL database

Now, let's create an Integration JUnit test for GET ALL Students REST API:
package net.javaguides.spirngboot;

import net.javaguides.spirngboot.entity.Student;
import net.javaguides.spirngboot.repository.StudentRepository;
import org.hamcrest.CoreMatchers;
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.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;

import java.util.List;

@SpringBootTest
@AutoConfigureMockMvc
class SpringbootTestcontainersDemoApplicationTests {

	@Autowired
	private StudentRepository studentRepository;

	@Autowired
	private MockMvc mockMvc;

	// given/when/then format - BDD style
	@Test
	public void givenStudents_whenGetAllStudents_thenListOfStudents() throws Exception {
		// given - setup or precondition
		List<Student> students =
				List.of(Student.builder().firstName("Ramesh").lastName("faadatare").email("[email protected]").build(),
				Student.builder().firstName("tony").lastName("stark").email("[email protected]").build());
		studentRepository.saveAll(students);

		// when - action
		ResultActions response = mockMvc.perform(MockMvcRequestBuilders.get("/api/students"));

		// then - verify the output
		response.andExpect(MockMvcResultMatchers.status().isOk());
		response.andExpect(MockMvcResultMatchers.jsonPath("$.size()", CoreMatchers.is(students.size())));
	}

}
Let's understand the above code.

We are using @SpringBootTest annotation to load the application context and test various components.

MockMvc provides support for Spring MVC testing. It encapsulates all web application beans and makes them available for testing. @AutoConfigureMockMvc annotation that can be applied to a test class to enable and configure auto-configuration of MockMvc.
@Autowired
private MockMvc mockMvc;
The MockMvc.perform() method will call a GET request method, which returns the ResultActions.
ResultActions response = mockMvc.perform(MockMvcRequestBuilders.get("/api/students"));
Using this result, we can have assertion expectations about the response, like its content, HTTP status, or header. 

The andExpect() will expect the provided argument. In our case, we're expecting HTTP status code and the size of the JSON array in the response:
// then - verify the output
response.andExpect(MockMvcResultMatchers.status().isOk());
response.andExpect(MockMvcResultMatchers.jsonPath("$.size()", CoreMatchers.is(students.size())));

7. Run Integration Test


What is the problem with the Integration test that we have written?

A common problem when writing integration tests is the dependency on installed components (Ex: MySQL, RabbitMQ) where the integration tests are supposed to run.

In our case, our Integration tests depend on the MySQL database. Installing a specific version of the MySQL database in every machine where the integration tests are supposed to run takes a lot of time right.

Basically, our integration tests depend on external services (installing MySQL, Rabbit MQ, Redis, etc) to run the integration tests right then how to reduce this dependency - what will be the solution.

The solution is Testcontainers.

8. What is Testcontainers?

Testcontainers is a Java library that supports JUnit tests, providing lightweight, throwaway instances of common databases, Selenium web browsers, or anything else that can run in a Docker container.

Using Testcontainers is fairly easy and it gives us the opportunity to create integration tests without the need for pre-installed components.

Using Testcontainers, we would always start with a clean database and our integration tests could run on any machine.

Testcontainer allows us to use Docker containers within our tests. As a result, we can write self-contained integration tests that depend on external resources.

9. Adding Testcontainers to Spring Boot Project

Open the pom.xml file and add the following Testcontainers dependencies:
<dependency>
	<groupId>org.testcontainers</groupId>
	<artifactId>testcontainers</artifactId>
	<version>1.16.2</version>
	<scope>test</scope>
</dependency>
<dependency>
	<groupId>org.testcontainers</groupId>
	<artifactId>junit-jupiter</artifactId>
	<version>1.16.2</version>
	<scope>test</scope>
</dependency>
<dependency>
	<groupId>org.testcontainers</groupId>
	<artifactId>mysql</artifactId>
	<version>1.16.2</version>
	<scope>test</scope>
</dependency>

Write Integration Tests using Testcontainers

Let's change the Integration test to use Testcontainers.

We gonna use the Singleton containers pattern to use Testcontainers.

Singleton containers pattern is useful to define a container that is only started once for several test classes.

Let us create a base class AbstractContainerBaseTest so that all our integration tests can extend without repeating the common configuration.
package net.javaguides.spirngboot;

import org.testcontainers.containers.MySQLContainer;

public class AbstractContainerBaseTest {

    static final MySQLContainer MY_SQL_CONTAINER;

    static {
        MY_SQL_CONTAINER = new MySQLContainer("mysql:latest");

        MY_SQL_CONTAINER.start();
    }
}

Now, simply extend our Integration class with the above AbstractContainerBaseTest:

package net.javaguides.spirngboot;

import net.javaguides.spirngboot.entity.Student;
import net.javaguides.spirngboot.repository.StudentRepository;
import org.hamcrest.CoreMatchers;
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.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;

import java.util.List;

@SpringBootTest
@AutoConfigureMockMvc
class SpringbootTestcontainersDemoApplicationTests extends AbstractContainerBaseTest{

	@Autowired
	private StudentRepository studentRepository;

	@Autowired
	private MockMvc mockMvc;

	// given/when/then format - BDD style
	@Test
	public void givenStudents_whenGetAllStudents_thenListOfStudents() throws Exception {
		// given - setup or precondition
		List<Student> students =
				List.of(Student.builder().firstName("Ramesh").lastName("faadatare").email("[email protected]").build(),
				Student.builder().firstName("tony").lastName("stark").email("[email protected]").build());
		studentRepository.saveAll(students);

		// when - action
		ResultActions response = mockMvc.perform(MockMvcRequestBuilders.get("/api/students"));

		// then - verify the output
		response.andExpect(MockMvcResultMatchers.status().isOk());
		response.andExpect(MockMvcResultMatchers.jsonPath("$.size()", CoreMatchers.is(students.size())));
	}

}

10. Demo

Before running the Integration test make sure that Docker is running on your machine otherwise, you won't be able to run the Integration test.

Here is the output of the above Integration test using Testcontainers:

GitHub Repository

The complete source code of this tutorial is on my GitHub repository at https://github.com/RameshMF/springboot-testcontainers-demo

Conclusion

In this tutorial, we have discussed how to perform Spring Boot application Integration Testing using Testcontainers.

If you want to learn more about Spring boot testing then highly suggest my Udemy course:

Testing Spring Boot Application with JUnit and Mockito (Includes Testcontainers)

Comments