/spring-boot-tc-mysql

Sample Spring Boot application that uses MySQL to perform integration tests by using TestContainer.

Primary LanguageJavaThe UnlicenseUnlicense

Spring Boot: MySQL Container Integration

Avoid running different databases between integration tests and production.

Dependabot Status Maven Build

Background

In general, we tend to use H2 to perform integration tests within the application. However there are scenarios where H2 may not give the same outcome as our actual database, such as MySQL. Such scenario is when you have a table column called rank or order.

Both names are allowed with H2 database but not with MySQL as those are reserved keywords. Therefore it is best to use the same database, in production environment, for our integration tests.

In this guide, we will implement MySQL Container, from TestContainers, with Spring Boot.

Dependencies

Full dependencies can be found in pom.xml.

Database

  • spring-boot-starter-data-jpa
  • spring-boot-starter-data-rest
  • mysql-connector-java

Integration tests

  • junit-jupiter from TestContainers
  • mysql from TestContainers

Implementation

Entity Class

Given we have a class called Book along with its repository class, BookRepository.

@Data
@Entity
public class Book {

    @Id
    @GeneratedValue
    private Long id;

    @Embedded
    private Author author;

    private String title;

}
public interface BookRepository extends JpaRepository<Book, Long> {
}

Test Implementation

Here we will be utilizing MySQL module from TestContainers to perform integration tests. The following implementation can be found in BookRepositoryRestResourceTests

Enable TestContainers

org.testcontainers:junit-jupiter dependency simplifies our implementation whereby the dependency will handle the
start and stop of the container.

We will start by informing @SpringBootTest that we will be using ContainerDatabaseDriver as our driver class
along with our JDBC URL

@Testcontainers
@SpringBootTest(
        properties = {
                "spring.jpa.generate-ddl=true",
                "spring.datasource.driver-class-name=org.testcontainers.jdbc.ContainerDatabaseDriver",
                "spring.datasource.url=jdbc:tc:mysql:8:///test
        }
)
public class BookRepositoryRestResourceTests {

}

We will trigger a REST call to create a Book and given that there is a database running, the book should be created.

@Testcontainers
@SpringBootTest(
        properties = {
                "spring.jpa.generate-ddl=true",
                "spring.datasource.driver-class-name=org.testcontainers.jdbc.ContainerDatabaseDriver",
                "spring.datasource.url=jdbc:tc:mysql:8:///test
        },
        webEnvironment = RANDOM_PORT
)
public class BookRepositoryRestResourceTests {

    @Autowired
    private TestRestTemplate restTemplate;

    @Test
    @DisplayName("Entity will be created if datasource is available")
    void create() {
        var author = author();

        var book = book(author);

        ResponseEntity<Book> response = restTemplate.postForEntity("/books", book, Book.class);

        assertThat(response.getStatusCode()).isEqualTo(CREATED);
    }

    private Author author() {
        var author = new Author();

        author.setName("Rudyard Kipling");

        return author;
    }

    private Book book(final Author author) {
        var book = new Book();

        book.setAuthor(author);
        book.setTitle("The Jungle Book");

        return book;
    }

}

Execute the test and you will get HTTP 200 or CREATED returned. To be certain that our test did run with MySQL Container, we should see the following content in the logs:

DEBUG 🐳 [mysql:8] - Starting container: mysql:8
...
org.hibernate.dialect.Dialect            : HHH000400: Using dialect: org.hibernate.dialect.MySQL8Dialect

This is how the application informing us that it is using MySQL Container which lead to Spring Boot automatically configure our dialect to MySQL8Dialect.

Verify MySQL availability

Another option is to verify that our application will connect to MySQL by triggering a check against Spring Boot Actuator Health endpoint.

public class DatasourceHealthTests {

    @Test
    @DisplayName("Database status will be UP and Database name should be MySQL")
    void databaseIsAvailable() throws JsonProcessingException {
        var response = restTemplate.getForEntity("/actuator/health", String.class);

        assertThat(response.getBody()).isNotNull();

        JsonNode root = new ObjectMapper().readTree(response.getBody());
        JsonNode dbComponentNode = root.get("components").get("db");

        String dbStatus = dbComponentNode.get("status").asText();
        String dbName = dbComponentNode.get("details").get("database").asText();

        assertThat(dbStatus).isEqualTo("UP");
        assertThat(dbName).isEqualTo("MySQL");
    }

}

Test above verifies that there's a running MySQL database connected to the application. Full implementation can be found in DatasourceHealthTests.

Conclusion

Now that we are running the same database as production environment, we can expect more accurate results from our integration tests.