Алексей
Алексей

Reputation: 53

Using custom DB docker-image with Testcontainers

I'm novice in Testcontainers, so i have a question. I have application on Spring/Hibernate. I have docker-image (h2testbase) with mysql-base (myTestDb) with data. I run that image in docker with -p 6161:3306. In test/resources directory I have file application.properties. It contains next

jdbc.driverClassName=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:6161/myTestDb?allowPublicKeyRetrieval=true&useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Europe/Moscow&&useSSL=false
jdbc.username=root
jdbc.cred=admin
hibernate.dialect=org.hibernate.dialect.MySQLDialect
hibernate.show_sql=true
hibernate.format_sql=true

I use mvn test - it's working. Now I want to run these test with Testcontainers. I added in pom.xml dependencies

<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>testcontainers</artifactId>
    <version>1.9.1</version>
</dependency>
    
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>mysql</artifactId>
    <version>1.9.1</version>
    <scope>test</scope>
</dependency>

I extented class MySQLContainer

public class TestMySQL extends MySQLContainer {

    public TestMySQL() {
        super();
    }

    public TestMySQL(String dockerImageName) {
        super(dockerImageName);
    }

    @Override
    public String getDriverClassName() {
        return "com.mysql.cj.jdbc.Driver";
    }
}

cuz MySQLContainer using com.mysql.jdbc.Driver and it's deprecated. My test (for example)

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {
        HibernateConfiguration.class,
        SecurityConfiguration.class,
        SecurityInitializer.class,
        ViewConfiguration.class,
        ViewInitializer.class})
@WebAppConfiguration
public class ControllerServiceJTest {

    @ClassRule
    public static TestMySQL  container
            = new TestMySQL("h2testbase");

    @Autowired
    ControllerService controllerService;

    @Test
    public void stationPagination() {
        Map<String, Object> pag = controllerService.stationPagination(4);
        Assert.assertTrue(((List<Station>)pag.get("stations")).size() == 8);
    }

    @Test
    public void trainPagination() {
        Map<String, Object> pag = controllerService.trainPagination(1);
        Assert.assertTrue(((List<Train>)pag.get("trains")).size() == 20);
    }

    @Test
    public void switchHelper() {
        Assert.assertTrue(controllerService.stationSwitchHelper("BLUE").equals(URLs.REDIRECT_DASHSTATION + "/2"));
    }
}

And there I'm hitting the wall. If I use mvn test, I see (through docker ps) that container is started. It started two or three times (and mapping go on random ports such 328xx), but then maven tells

org.testcontainers.containers.ContainerLaunchException: Container startup failed
Caused by: org.rnorth.ducttape.RetryCountExceededException: Retry limit hit with exception
Caused by: org.testcontainers.containers.ContainerLaunchException: Could not create/start container
Caused by: org.rnorth.ducttape.TimeoutException: org.rnorth.ducttape.TimeoutException: java.util.concurrent.TimeoutException
Caused by: org.rnorth.ducttape.TimeoutException: java.util.concurrent.TimeoutException
Caused by: java.util.concurrent.TimeoutException

What should I do now? How to tell to my testcontainer needed port (6161)? How to use parameters which in application.properties? I can't find code examples where used custom images with DB with data. Thank you in advance

Update Add full result for failed test.

[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running ru.javasch.metro.junit.ControllerServiceJTest
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
SLF4J: Failed to load class "org.slf4j.impl.StaticMDCBinder".
SLF4J: Defaulting to no-operation MDCAdapter implementation.
SLF4J: See http://www.slf4j.org/codes.html#no_static_mdc_binder for further details.
        ?? Checking the system...
        ? Docker version should be at least 1.6.0
        ? Docker environment should have more than 2GB free disk space
[ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 97.189 s <<< FAILURE! - in ru.javasch.metro.junit.ControllerServiceJTest
[ERROR] ru.javasch.metro.junit.ControllerServiceJTest  Time elapsed: 97.187 s  <<< ERROR!
org.testcontainers.containers.ContainerLaunchException: Container startup failed
Caused by: org.rnorth.ducttape.RetryCountExceededException: Retry limit hit with exception
Caused by: org.testcontainers.containers.ContainerLaunchException: Could not create/start container
Caused by: org.rnorth.ducttape.TimeoutException: org.rnorth.ducttape.TimeoutException: java.util.concurrent.TimeoutException
Caused by: org.rnorth.ducttape.TimeoutException: java.util.concurrent.TimeoutException
Caused by: java.util.concurrent.TimeoutException

There some info. I tried tests with MySqlContainer from here (with my TestMySql). When I use clean mysql:5.5 image - all good. But when I try add some modifications in container (for example addFixedExposedPort) it's not start cuz port is already allocated. In case when I adding data from script - it's "Could not create container". If I'm trying give it my image (h2testbase), again "Could not create container".

Upvotes: 2

Views: 3152

Answers (2)

Sebastian
Sebastian

Reputation: 1

I had the same problem. It seems that when extending a custom class from MySQLContainer the container starts multiple times. In my case three mysql container were started. When I use directly MySQLContainer class everything worked as expected. Only one mysql container is started.

Upvotes: 0

Ilya
Ilya

Reputation: 2167

It seems you have two problems here.

  1. Docker is exposing mysql server on a random port, but you need a fixed port. To fix that you may set the fixed port using addFixedExposedPort of GenericContainer

    public class TestMySQL extends MySQLContainer {
    
        public TestMySQL(String dockerImageName) {
            super(dockerImageName);
            addFixedExposedPort(6161, MYSQL_PORT);
        }
    
        @Override
        public String getDriverClassName() {
            return "com.mysql.cj.jdbc.Driver";
        }
    }
    
  2. You probably don't have database test, user test with password test as it's default credentials in MySQLContainer, that cause the ContainerLaunchException you are getting. Use withDatabaseName, withUsername and withPassword to configure DB and user properly.

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(classes = {
        HibernateConfiguration.class,
        SecurityConfiguration.class,
        SecurityInitializer.class,
        ViewConfiguration.class,
        ViewInitializer.class})
    @WebAppConfiguration
    public class ControllerServiceJTest {
    
        @ClassRule
        public static TestMySQL  container
            = new TestMySQL("h2testbase")
              .withDatabaseName("myTestDb")
              .withUsername("root")
              .withPassword("admin");
    
        @Autowired
        ControllerService controllerService;
    
        @Test
        public void stationPagination() {
            Map<String, Object> pag = controllerService.stationPagination(4);
            Assert.assertTrue(((List<Station>)pag.get("stations")).size() == 8);
        }
    
        @Test
        public void trainPagination() {
            Map<String, Object> pag = controllerService.trainPagination(1);
            Assert.assertTrue(((List<Train>)pag.get("trains")).size() == 20);
        }
    
        @Test
        public void switchHelper() {
            Assert.assertTrue(controllerService.stationSwitchHelper("BLUE").equals(URLs.REDIRECT_DASHSTATION + "/2"));
        }
    }
    

UPDATE:

To turn on logging, add following dependency to pom.xml

    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.2.1</version>
    </dependency>

and create src/test/resources/logback.xml with following content

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
        </encoder>
    </appender>

    <root level="info">
        <appender-ref ref="STDOUT"/>
    </root>

    <logger name="org.testcontainers" level="DEBUG"/>
</configuration

Upvotes: 0

Related Questions