[JAVA] Démarrez le conteneur MySQL uniquement pendant le test Spring Boot + MyBatis avec Testcontainers

introduction

Vous pouvez utiliser Testcontainers (https://www.testcontainers.org/) pour démarrer un conteneur MySQL uniquement lors du test de JUnit.

Exemple de code SimpleMySQLTest.java Il semble être assez facile à utiliser, mais j'ai eu beaucoup de mal quand je l'ai essayé avec Spring Boot + MyBatis, donc je vais résumer son fonctionnement.

Tout d'abord, exécutez Spring Boot + MyBatis normalement

Avant d'essayer le test avec Testcontainers, écrivez du code fonctionnel.

Créer du code pour MyBatis

Créez trois fichiers: UserRepositoryImpl.java, UserMapper.java et UserMapper.xml.

UserRepositoryImpl.java


package springdockerexample.infrastructure.user;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import springdockerexample.domain.user.Name;
import springdockerexample.domain.user.User;
import springdockerexample.domain.user.UserRepository;
import springdockerexample.domain.user.Users;

import java.util.List;

@Repository
public class UserRepositoryImpl implements UserRepository {
  @Autowired
  private UserMapper mapper;

  @Override
  public Users findAll() {
    List<User> users = mapper.selectAll();
    return new Users(users);
  }
}

UserMapper.java


package springdockerexample.infrastructure.user;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import springdockerexample.domain.user.Name;
import springdockerexample.domain.user.User;

import java.util.List;

@Mapper
public interface UserMapper {
  List<User> selectAll();
}

UserMapper.xml


<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="springdockerexample.infrastructure.user.UserMapper">

    <resultMap id="user" type="springdockerexample.domain.user.User">
        <result property="name.value" column="name"/>
        <result property="age.value" column="age"/>
    </resultMap>

    <select id="selectAll" resultMap="user">
        SELECT name, age FROM users
    </select>
</mapper>

Préparer le fichier de propriété

Écrivez les informations de connexion à la base de données dans application.yaml.

application.yaml


spring:
  datasource:
    url: jdbc:mysql://localhost/mydb
    username: user
    password: password
    driverClassName: com.mysql.cj.jdbc.Driver

Préparer les données de test

Préparez les données pour le développement et les tests dans un fichier.

sql:src/test/resources/docker-entrypoint-initdb.d/init.sql


USE `mydb`;

CREATE TABLE `users` (
  `name` VARCHAR(255) NOT NULL,
  `age` int NOT NULL
);

INSERT INTO `users` (`name`, `age`) VALUES
('Alice', 20),
('Bob', 30);

Lancer le conteneur localement

Pour vérifier le fonctionnement de Spring Boot + MyBatis, démarrez MySQL avec Docker Compose.

A ce stade, en montant le fichier SQL préparé précédemment sur docker-entrypoint-initdb.d, les données seront insérées automatiquement.

docker-compose.yaml


version: '3'
services:

  my-db:
    image: mysql:5.7.25
    ports:
      - 3306:3306
    volumes:
      - ./src/test/resources/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: mydb
      MYSQL_USER: user
      MYSQL_PASSWORD: password

Contrôle de fonctionnement

Si vous incluez spring-dev-tools, il commencera par ./mvnw spring-boot: run.

$ ./mvnw spring-boot:run
$ curl localhost:8080/users
{"users":[{"name":"Alice","age":20},{"name":"Bob","age":30}]}

Cela fonctionne en toute sécurité.

De cette façon, il est possible de tester avec un conteneur démarré avec Docker Compose, mais il n'est pas possible de redémarrer le conteneur pour chaque cas de test.

Utilisez Testcontainers pour lancer des conteneurs à volonté pendant les tests JUnit.

Démarrez le conteneur uniquement lorsque JUnit s'exécute dans Testcontainers

Démarrez MySQL pour tester les paramètres d'informations de connexion

Pour les tests, reportez-vous à "Préparer un conteneur de base de données jetable avec des conteneurs de test et tester l'application Spring Boot" Créez les fichiers suivants pour démarrer le conteneur MySQL et définissez les informations de connexion sur le contexte.

MySQLContainerContextInitializer.java


package springdockerexample.testhelper;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.testcontainers.containers.BindMode;
import org.testcontainers.containers.MySQLContainer;
import org.testcontainers.containers.output.Slf4jLogConsumer;

public class MySQLContainerContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
  private static final String MYSQL_IMAGE = "mysql:5.7.25";

  private static final String DATABASE_NAME = "mydb";
  private static final String USERNAME = "user";
  private static final String PASSWORD = "password";
  private static final int PORT = 3306;

  private static final String INIT_SQL = "docker-entrypoint-initdb.d/init.sql";
  private static final String INIT_SQL_IN_CONTAINER = "/docker-entrypoint-initdb.d/init.sql";

  private static final Logger LOGGER = LoggerFactory.getLogger(MySQLContainerContextInitializer.class);

  private static final MySQLContainer MYSQL = (MySQLContainer) new MySQLContainer(MYSQL_IMAGE)
          .withDatabaseName(DATABASE_NAME)
          .withUsername(USERNAME)
          .withPassword(PASSWORD)
          .withExposedPorts(PORT)
          .withLogConsumer(new Slf4jLogConsumer(LOGGER))
          .withClasspathResourceMapping(INIT_SQL, INIT_SQL_IN_CONTAINER, BindMode.READ_ONLY);

  static  {
    MYSQL.start();
  }

  @Override
  public void initialize(ConfigurableApplicationContext context) {
    String mysqlJdbcUrl = MYSQL.getJdbcUrl();
    TestPropertyValues.of("spring.datasource.url=" + mysqlJdbcUrl)
            .applyTo(context.getEnvironment());
  }
}

Je vais expliquer ce contenu dans l'ordre.

Paramètres du conteneur MySQL

  private static final MySQLContainer MYSQL = (MySQLContainer) new MySQLContainer(MYSQL_IMAGE)
          .withDatabaseName(DATABASE_NAME)
          .withUsername(USERNAME)
          .withPassword(PASSWORD)
          .withExposedPorts(PORT)
          .withLogConsumer(new Slf4jLogConsumer(LOGGER))
          .withClasspathResourceMapping(INIT_SQL, INIT_SQL_IN_CONTAINER, BindMode.READ_ONLY);

Ici, presque les mêmes paramètres que ceux écrits dans docker-compose.yaml précédemment sont écrits en Java.

Le nom de l'image est spécifié dans le nouveau MySQLContainer (MYSQL_IMAGE), et des paramètres tels que le nom de la base de données, le nom d'utilisateur et le mot de passe sont également définis ici.

De plus, withClasspathResourceMapping monte le SQL pour l'initialisation de la base de données. Il semble que vous puissiez également monter le fichier de configuration MySQL.

Démarrer le conteneur MySQL

  static  {
    MYSQL.start();
  }

Je démarre un conteneur MySQL. MySQL.start () doit être exécuté avant MySQL.getJdbUrl () dans initialize décrit ci-dessous.

Je saute cet exemple, mais je pense qu'il vaut mieux appeler MySQL.stop () à la fin.

Paramètres des informations de connexion

Étant donné que le port ** de connexion à MySQL démarré par Testcontainers est attribué dynamiquement **, il est nécessaire de définir dynamiquement les informations de connexion en fonction de cela.

J'ai créé une classe appelée MySQLContainerContextInitializer pour modifier dynamiquement les paramètres de Spring Boot.

La méthode de réglage spécifique est la suivante.

  @Override
  public void initialize(ConfigurableApplicationContext context) {
    String mysqlJdbcUrl = MYSQL.getJdbcUrl();
    TestPropertyValues.of("spring.datasource.url=" + mysqlJdbcUrl)
            .applyTo(context.getEnvironment());
  }

En passant, il est nécessaire d'obtenir les informations de connexion à MySQL à partir de l'instance MySQLContainer [SimpleMySQLTest.java](https://github.com/testcontainers/testcontainers-java/blob/master/modules/jdbc-test/src Vous pouvez également voir dans la partie suivante de /test/java/org/testcontainers/junit/SimpleMySQLTest.java).

SimpleMySQLTest.java


    @NonNull
    protected ResultSet performQuery(MySQLContainer containerRule, String sql) throws SQLException {
        HikariConfig hikariConfig = new HikariConfig();
        hikariConfig.setDriverClassName(containerRule.getDriverClassName());
        hikariConfig.setJdbcUrl(containerRule.getJdbcUrl());
        hikariConfig.setUsername(containerRule.getUsername());
        hikariConfig.setPassword(containerRule.getPassword());

        HikariDataSource ds = new HikariDataSource(hikariConfig);
        Statement statement = ds.getConnection().createStatement();
        statement.execute(sql);
        ResultSet resultSet = statement.getResultSet();

        resultSet.next();
        return resultSet;
    }

Description du test

Voici le test Spring Boot comme d'habitude.

UserRepositoryImplTest.java


package springdockerexample.infrastructure.user;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import springdockerexample.domain.user.UserRepository;
import springdockerexample.domain.user.Users;
import springdockerexample.testhelper.MySQLContainerContextInitializer;

import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;

@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(initializers = { MySQLContainerContextInitializer.class })
public class UserRepositoryImplTest {

  @Autowired
  UserRepository userRepository;

  @Test
  public void test() {
    Users users = userRepository.findAll();
    assertThat(users.count(), is(2));
  }
}

Le but est de spécifier la classe créée précédemment avec @ContextConfiguration (initializers = {SQLContainerContextInitializer.class}), de démarrer MySQL et de définir les informations de connexion.

Après cela, vous pouvez écrire un test JUnit normal et accéder à la base de données sans aucun problème.

Résumé

En résumé, ce n'est pas du tout difficile, mais en réalité c'était assez difficile. Cela peut être utile une fois que vous l'avez configuré.

Puisque Testcontainers prend en charge tout conteneur autre que DB, vous pouvez effectuer librement des tests automatiques.

La structure de fichier finale du contenu de cet article est la suivante.

$ tree
.
├── pom.xml
├── ...
└── src
    ├── main
    │   ├── java
    │   │   └── springdockerexample
    │   │       ├── SpringDockerExampleApplication.java
    │   │       ├── ...
    │   │       └── infrastructure
    │   │           └── user
    │   │               ├── UserMapper.java
    │   │               └── UserRepositoryImpl.java
    │   └── resources
    │       ├── application.yaml
    │       └── springdockerexample
    │           └── infrastructure
    │               └── user
    │                   └── UserMapper.xml
    └── test
        ├── java
        │   └── springdockerexample
        │       ├── SpringDockerExampleApplicationTests.java
        │       ├── infrastructure
        │       │   └── user
        │       │       └── UserRepositoryImplTest.java
        │       └── testhelper
        │           └── MySQLContainerContextInitializer.java
        └── resources
            └── docker-entrypoint-initdb.d
                └── init.sql

Le code source est ici.

Recommended Posts

Démarrez le conteneur MySQL uniquement pendant le test Spring Boot + MyBatis avec Testcontainers
Effectuer un test de confirmation de transaction avec Spring Boot
Démarrez le développement d'applications Web avec Spring Boot
Implémenter CRUD avec Spring Boot + Thymeleaf + MySQL
Test de validation de classe de formulaire avec Spring Boot
Testez le contrôleur avec Mock MVC dans Spring Boot
Traitement asynchrone avec exécution régulière dans Spring Boot
Jusqu'à l'acquisition de données avec Spring Boot + MyBatis + PostgreSQL
Comment utiliser MyBatis2 (iBatis) avec Spring Boot 1.4 (Spring 4)
Créez une application CRUD avec Spring Boot 2 + Thymeleaf + MyBatis
Paramètres de connexion à MySQL avec Spring Boot + Spring JDBC
Essayez d'utiliser un conteneur DI avec Laravel et Spring Boot
[Compatible JUnit 5] Ecrire un test en utilisant JUnit 5 avec Spring boot 2.2, 2.3
[JUnit 5] Ecrivez un test de validation avec Spring Boot! [Test de paramétrage]
Testez la classe injectée sur le terrain dans le test de démarrage Spring sans utiliser le conteneur Spring
Jusqu'à ce que vous commenciez à développer avec Spring Boot dans eclipse 1
Jusqu'à ce que vous commenciez à développer avec Spring Boot dans eclipse 2
J'ai écrit un test avec Spring Boot + JUnit 5 maintenant
Télécharger avec Spring Boot
Générer un code à barres avec Spring Boot
Implémenter GraphQL avec Spring Boot
Démarrez avec Spring Boot
Bonjour tout le monde avec Spring Boot!
Exécutez LIFF avec Spring Boot
Connexion SNS avec Spring Boot
Téléchargement de fichiers avec Spring Boot
Spring Boot commençant par copie
Spring Boot à partir de Docker
Définir des cookies avec Spring Boot
Utiliser Spring JDBC avec Spring Boot
Ajouter un module avec Spring Boot
Premiers pas avec Spring Boot
Envoyer du courrier avec Spring Boot
Implémentez un serveur API Web REST simple avec Spring Boot + MySQL
02. J'ai créé une API pour me connecter de Spring Boot à MySQL (My Batis)
Exemple de code pour le test unitaire d'un contrôleur Spring Boot avec MockMvc
Image de l'application Spring Boot à l'aide de jib-maven-plugin et lancez-la avec Docker