Integration Testing with Spring Boot, MySQL and Testcontainers

If you’re an absolute beginner looking to learn about integration testing for Spring Boot applications with a MySQL database server, then you’re in the right place. As you proceed through this tutorial, remember that everyone was once a beginner, and the only silly question is the one you don’t ask.

This tutorial aims to introduce you to Testcontainers – a Java library that simplifies integration tests by providing lightweight, throwaway instances of common databases, Selenium web browsers, or anything else that can run in a Docker container.

You will learn how to create a simple Spring Boot application that communicates with a MySQL database server, all running locally on your machine. Don’t worry if that sounds complicated – I will walk you through each step, explaining what everything does and how it all fits together.

Let’s begin!

What are Testcontainers?

Let’s start with understanding what Testcontainers are. Testcontainers is a Java library designed to simplify your testing experience. This library helps you set up and tear down disposable instances of specific software – most commonly, databases like MySQL, or message brokers like Kafka or Rabbit MQ – in Docker containers. This allows us to write tests in Java which interact with these resources, much as we would in a live production environment.

Why Use Testcontainers for Integration Testing?

Now, why would we want to use Testcontainers for integration testing? When we’re testing an application, it’s incredibly important that our tests run in an environment that closely resembles our production environment.

In the real world, our application will be talking to a real database – it won’t know or care whether that database is on the same machine or halfway around the world. So, to make our integration tests truly valuable, we should try to mimic this situation as closely as possible. Testcontainers helps us do exactly that by providing a real database (or another service) running inside a Docker container. This helps us catch and fix issues that we might miss with a simpler, less realistic setup.

How Do Testcontainers Work?

So, how do Testcontainers actually work? The answer is Docker! Docker is a platform that allows us to “containerize” our applications along with their dependencies, running them in isolated environments called containers.

When you run your tests, Testcontainers will use Docker to pull the necessary images (like MySQL, for example), create new containers from these images, and run these containers. Your test code can then interact with these containers as if they were the real services, connecting to a database or making web requests.

Once the tests are complete, Testcontainers will automatically stop and remove the containers, ensuring that your testing environment is kept clean and ready for the next run.

To learn more about Docker, please check out my Docker tutorials for beginners.

About Spring Boot Application

In this tutorial, we will create a very basic Spring Boot application that exposes a REST API endpoint for managing user details. Specifically, it will have a single /users endpoint that accepts a POST request containing User details such as firstName, lastName, email, and password. When a request is made, the application will store these details in a MySQL database using Spring Data JPA, a part of Spring Framework that simplifies database operations.

Creating a New Spring Boot Application

First, we need to set up the Spring Boot project. You can do this using Spring Initializr https://start.spring.io/ or through your preferred IDE. So, create a new Spring Boot project and add the following maven dependencies to the pom.xml file:

  • Spring Web,
  • Spring Data JPA,
  • MySQL Driver,

After generating the project, open it in your IDE.

Setting up the Database

Next, we can configure the MySQL connection details in our application.properties file located in src/main/resources. This is needed only for your Spring Boot application to be able to connect to MySQL server.

However, this is not needed for integration testing with Testcontainers. The test case that we will write in this tutorial, will not use MySQL connection details configured in the application.properties file. So, for testing purposes this is optional.

spring.datasource.url=jdbc:mysql://localhost:3306/testdb
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.hibernate.ddl-auto=update

Note: Remember to replace the above values with your actual MySQL database details.

Creating the User Entity

Let’s create the User entity class. Navigate to the main package and create a new class named User.

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String firstName;
    private String lastName;
    private String email;
    private String password;

    // Getters and Setters
}

Creating the User Repository

Next, we create a User Repository interface that extends JpaRepository.

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}

Creating the REST API

Now, let’s create a simple POST API to save the user details in the database.

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {
    private final UserRepository userRepository;

    public UserController(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @PostMapping("/users")
    public User createUser(@RequestBody User user) {
        return userRepository.save(user);
    }
}

This is the basic setup of our application. Now let’s proceed to setup Testcontainers for integration testing.

Add Testcontainers to a Spring Boot Project

To configure our Spring Boot application to be able to use Testcontainers, we will need to add a few additional dependencies. Below is a list of dependencies, you will need to add depending on the build tool you use.

Gradle:

testImplementation 'org.springframework.boot:spring-boot-testcontainers:3.3.0'
testImplementation 'org.testcontainers:testcontainers:1.19.8'
testImplementation 'org.testcontainers:mysql:1.19.8'

Maven:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-testcontainers</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>junit-jupiter</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>mysql</artifactId>
    <scope>test</scope>
</dependency>

Creating a Testcontainer

We can how create a test class and create a MySQL test container in it.

@Testcontainers
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class UsersControllerTestContainersTest {
 

    @Container
    @ServiceConnection
    static MySQLContainer<?> mySQLContainer = new MySQLContainer<>("mysql:latest");



}

@Testcontainers: This is an annotation provided by the Testcontainers library. When you annotate your test class with @Testcontainers, it tells the Testcontainers library to manage the lifecycle of the containers you define in your test class. This means that before your tests run, Testcontainers will start the containers you’ve defined, and after your tests are done, it will stop and clean up those containers.

@Container: This annotation tells Testcontainers that the field mySQLContainer represents a container that should be started and stopped as part of the test lifecycle.

@ServiceConnection: This annotation also comes from the Testcontainers library and it tells Testcontainers that the container represented by mySQLContainer should be connected to the application under test. This means that Testcontainers will automatically configure your application to use the MySQL instance running in the container.

= new MySQLContainer<>("mysql:latest"): This part creates a new instance of the MySQLContainer class, passing in the string "mysql:latest" as a parameter. This tells Testcontainers to use the latest version of the MySQL Docker image when creating the container.

Test if the Testcontainer is running

Now that we have created a test container we can check if it was actually created and if it is running. We can do it with the following test method.

@Test
void testConnectionIsEstablished() {
    assertTrue(mySQLContainer.isCreated());
    assertTrue(mySQLContainer.isRunning());
}
This test method is like a watchdog, keeping an eye on our MySQL container to make sure everything is up and running smoothly.
First, it asks the container, “Have you been created yet?” by calling mySQLContainer.isCreated(). If the container responds with a “Yes, I’m here!”, the test passes that checkpoint. If not, well, it means something went wrong during the creation process, and the test will fail.
Next, it follows up with another question: “Alright, but are you actually running?” This time, it calls mySQLContainer.isRunning(). If the container gives a thumbs up, the test is happy and moves on. But if the container is not running then the test will fail this checkpoint too.

Complete Test class

Finally, let’s write the test for our UserController.

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;

import static org.assertj.core.api.Assertions.assertThat;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.testcontainers.containers.MySQLContainer;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Testcontainers
public class UserControllerTest {

    @Container
    @ServiceConnection
    static MySQLContainer<?> mySQLContainer = new MySQLContainer<>("mysql:latest");

    @LocalServerPort
    private int port;

    @Autowired
    private TestRestTemplate restTemplate;
 
    @Test
    public void testCreateUser() {
        User user = new User();
        user.setFirstName("Test");
        user.setLastName("User");
        user.setEmail("[email protected]");
        user.setPassword("testpass");

        User response = restTemplate.postForObject("http://localhost:" + port + "/users", user, User.class);
        assertThat(response).isNotNull();
        assertThat(response.getId()).isNotNull();
    }
}

In short, this is a test class that starts a MySQL container, starts the Spring Boot application, and runs an end-to-end test that interacts with the database through the HTTP endpoint. After the test is run, it cleans up the connection pool and stops the MySQL container.

To learn more about testing Java code, please check Testing Java code tutorials.