Spring Boot TestContainers Mapped port can only be obtained after the container is started
Asked Answered
A

5

39

I'm trying to add automated testing using the TestContainers library to my Spring Boot project

Here is my test class to test my jpa repository:

package com.ubm.mfi.repo;

import com.ubm.mfi.domain.MasterFileIndexRow;
import org.junit.ClassRule;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Testcontainers;

import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;

@ExtendWith(SpringExtension.class)
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@Testcontainers
@ContextConfiguration(initializers = { MasterFileIndexRowRepoTest.Initializer.class })
public class MasterFileIndexRowRepoTest {

    @ClassRule
    public static PostgreSQLContainer<?> postgreSQLContainer = new PostgreSQLContainer<>("postgres:latest");

    @Autowired
    private MasterFileIndexRowRepo masterFileIndexRowRepo;

    // write test cases here
    @Test
    public void whenFindAllRows_thenSizeIsGreaterThanZero() {
        // when
        List<MasterFileIndexRow> rows = masterFileIndexRowRepo.findAll();

        // then
        assertThat(rows.size())
                .isGreaterThan(0);
    }

    static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {

        @Override
        public void initialize(ConfigurableApplicationContext configurableApplicationContext) {

            TestPropertyValues
                    .of("spring.datasource.url=" + postgreSQLContainer.getJdbcUrl(),
                            "spring.datasource.username=" + postgreSQLContainer.getUsername(),
                            "spring.datasource.password=" + postgreSQLContainer.getPassword())
                    .applyTo(configurableApplicationContext.getEnvironment());

        }

    }

}

Here are the dependencies in my build.gradle

testCompile "org.testcontainers:testcontainers:1.14.1"
testCompile "org.testcontainers:postgresql:1.14.1"

When running the Test I get this error: Caused by: java.lang.IllegalStateException: Mapped port can only be obtained after the container is started

From what I've seen, the container should start when starting the test, does anybody know what I'm missing?

Amaleta answered 6/5, 2020 at 0:24 Comment(2)
this error may be induced by another error -> github.com/testcontainers/testcontainers-java/issues/3609 Check if you have "Could not connect to Ryuk at localhost:49154" in your logs.Barrington
I also had this issue when I had forgotten to use the @Testcontainers annotation on the classTopdress
B
23

You are trying to use PostgresSQLContainer as JUnit ClassRule but your usage of @ExtendWith seems to indicate that you are using JUnit 5 / Jupiter which does not support JUnit 4 rules.

Use the JUnit 5 integration of Testcontainers instead: https://www.testcontainers.org/test_framework_integration/junit_5/

Bettinabettine answered 6/5, 2020 at 5:56 Comment(1)
Alternatively, the JDBC integration can be used: testcontainers.org/modules/databases/jdbcFlicker
J
15

Not sure if it does help to smdy but for me starting the actual test container after its definition solved the problem:

@Container
private static final PostgreSQLContainer DATABASE = new PostgreSQLContainer("postgres:14");

static {
    DATABASE.start();
}

// any kind of DynamicPropertySources or initializers 
// goes below and can successfully get mapped port from DATABASE.getJdbcUrl()
Jason answered 16/9, 2022 at 13:14 Comment(1)
.start() works too for new MSSQLServerContainer<>(). But just realized(at least for JUnit5), we should not be using .start(). There's @Testcontainers and @Container for that.Parke
C
2

This problem occured when I still used Junit 4 org.junit.Test to annotate test methods. Adding the explicit start command as suggested by Dzmitry worked for me in this case. As the more appropriate remediation, I annotated my test methods with Junit 5 org.junit.jupiter.api.Test annotation. In this case, the containers start without an explicit start command.

Below worked for me

import org.junit.jupiter.api.Test;

import org.testcontainers.containers.PostgreSQLContainer;

import org.testcontainers.junit.jupiter.Container;

import org.testcontainers.junit.jupiter.Testcontainers;

@Testcontainers
public class DataBaseConnectionTest {

    @Container
    private static final PostgreSQLContainer<?> databaseContainer = new PostgreSQLContainer<>("postgres:14");

    ...
    @Test
    void test1() {
    System.out.println(databaseContainer..getJdbcUrl());
    }

}

The following are my Maven dependencies:

    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-api</artifactId>
        <version>5.9.1</version>
        <scope>test</scope>
    </dependency>
    
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-engine</artifactId>
        <version>5.9.1</version>
        <scope>test</scope>
    </dependency>
    
    <dependency>
        <groupId>org.testcontainers</groupId>
        <artifactId>junit-jupiter</artifactId>
        <version>1.17.5</version>
        <scope>test</scope>
    </dependency>
   
    <dependency>
        <groupId>org.testcontainers</groupId>
        <artifactId>testcontainers</artifactId>
        <version>1.17.5</version>
        <scope>test</scope>
    </dependency>
    
    <dependency>
        <groupId>org.testcontainers</groupId>
        <artifactId>postgresql</artifactId>
        <version>1.17.5</version>
        <scope>test</scope>
    </dependency>
Cosenza answered 19/10, 2022 at 10:11 Comment(0)
E
1

When you use @TestInstance(Lifecycle.PER_CLASS) better declare your test containers in an interface and import in test class like

@Testcontainers
public interface Containers {

    @Container @ServiceConnection
    public static final PostgreSQLContainer<?> postgres =
            new PostgreSQLContainer<>("postgres:latest");
}
@TestInstance(Lifecycle.PER_CLASS)
@ImportTestcontainers(Containers.class)
class TestClass {
  // here you can use all @BeforeAll and other lifecycle methods

}

Do not start and stop containers - it's taken care by @TestContainers

Excerpt from the Javadoc of @TestContainers

@Testcontainers is a JUnit Jupiter extension to activate automatic startup and stop of containers used in a test case.

The Testcontainers extension finds all fields that are annotated with Container and calls their container lifecycle methods. Containers declared as static fields will be shared between test methods. They will be started only once before any test method is executed and stopped after the last test method has executed. Containers declared as instance fields will be started and stopped for every test method.

The annotation @Testcontainers can be used on a superclass in the test hierarchy as well. All subclasses will automatically inherit support for the extension.

Note: This extension has only been tested with sequential test execution. Using it with parallel test execution is unsupported and may have unintended side effects.

Elenor answered 15/3 at 16:58 Comment(0)
G
0

The solution as mentioned here is to start the container after the declaration. I only had this issue when I changed the lifecycle of my tests to per class.

Germanium answered 8/10, 2023 at 3:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.