Quarkus: Cannot use TestContainers, QuarkusTest and JPA together

Created on 12 Apr 2019  路  10Comments  路  Source: quarkusio/quarkus

This should work and it does not:

@Testcontainers
@QuarkusTest
public class TodoResourceTest {

    @Container
    PostgreSQLContainer DATABASE = new PostgreSQLContainer<>()
            .withDatabaseName("rest-crud")
            .withUsername("restcrud")
            .withPassword("restcrud")
            .withExposedPorts(5432)
            .withCreateContainerCmdModifier(cmd -> {
                cmd
                        .withHostName("localhost")
                        .withPortBindings(new PortBinding(Ports.Binding.bindPort(5432), new ExposedPort(5432)));
            });

    @Test
    public void testInitialItems() {
        System.out.println(DATABASE.getJdbcUrl());
        String todos = get("/api").asString();
        System.out.println(todos);
    }

}

This is because the server is started before the database and connect to it immediately:

Caused by: java.lang.RuntimeException: Failed to start quarkus
Caused by: javax.persistence.PersistenceException: [PersistenceUnit: default] Unable to build Hibernate SessionFactory
Caused by: org.hibernate.exception.GenericJDBCException: Unable to open JDBC Connection for DDL execution
Caused by: java.sql.SQLException: Exception while creating new connection
Caused by: java.util.concurrent.ExecutionException: java.lang.RuntimeException: Exception while creating new connection
Caused by: java.lang.RuntimeException: Exception while creating new connection
Caused by: org.postgresql.util.PSQLException: Connection to localhost:5432 refused. Check that the hostname and port are correct and that the postmaster is accepting TCP/IP connections.
Caused by: java.net.ConnectException: Connection refused (Connection refused)

The (ugly) workaround is:

@QuarkusTest
public class TodoResourceTest {

    @BeforeAll
    public static void init() {
        PostgreSQLContainer DATABASE = new PostgreSQLContainer<>()
                .withDatabaseName("rest-crud")
                .withUsername("restcrud")
                .withPassword("restcrud")
                .withExposedPorts(5432)
                .withCreateContainerCmdModifier(cmd -> {
                    cmd
                            .withHostName("localhost")
                            .withPortBindings(new PortBinding(Ports.Binding.bindPort(5432), new ExposedPort(5432)));
                });

        DATABASE
                .start();

        System.out.println(DATABASE.getJdbcUrl());
    }

    @Test
    public void testInitialItems() {
        String todos = get("/api").asString();
        System.out.println(todos);
    }

}
triaginvalid

All 10 comments

Hi @cescoffier,
Although this does not seem to be Testcontainers' issue, If I may, I would like to suggest a few things here:
1) static port binding here is dangerous and most probably will break on CI env. I would suggest using random ports and either framework's test config mechanism or system properties like here. If you need PostgreSQL on port 5432 for debugging with your favourite tools, you can use the approach I described in my recent article

2) another option would be to use the JDBC URL support, it makes starting with Testcontainers super easy

I hope that helps :)

@bsideup Thanks!

We have lots of improvement to do on this subject (the code is for a demo - no CI :-D). Typically, the JDBC url is configured in the application.properties file, we would need to provide a new value at runtime.

So, in theory, we should:

  1. start the container
  2. retrieve the random port and host
  3. pass this value to the application
  4. start the application

Well, if you use the JDBC URL-like containers, you can easily put it to application.properties like here. The JDBC driver SPI will do the magic.

For other types of containers: yes, that's the algorithm :)

@cescoffier I think you could get around the problem is a slightly less ugly way like so:

@Container
static PostgreSQLContainer DATABASE = new PostgreSQLContainer<>()
            .withDatabaseName("rest-crud")
            .withUsername("restcrud")
            .withPassword("restcrud")
            .withExposedPorts(5432)
            .withCreateContainerCmdModifier(cmd -> {
                cmd
                        .withHostName("localhost")
                        .withPortBindings(new PortBinding(Ports.Binding.bindPort(5432), new ExposedPort(5432)));
            });

with the use of static you wouldn't have to add the boiler plate of beforeAll.

@geoand I've tried that, the application starts before the container so the connection failed.

@cescoffier weird...

I changed our quarkus-integration-test-jpa-postgresql integration test, by removing the docker maven plugin and altered the test code to be:

@Testcontainers
@QuarkusTest
public class JPAFunctionalityTest {

    @Container
    public static PostgreSQLContainer DATABASE = new PostgreSQLContainer<>("postgres:10.5")
            .withDatabaseName("hibernate_orm_test")
            .withUsername("hibernate_orm_test")
            .withPassword("hibernate_orm_test")
            .withExposedPorts(5432)
            .withCreateContainerCmdModifier(cmd -> {
                cmd
                        .withHostName("localhost")
                        .withPortBindings(new PortBinding(Ports.Binding.bindPort(5432), new ExposedPort(5432)));
            });

    @Test
    public void testJPAFunctionalityFromServlet() throws Exception {
        RestAssured.when().get("/jpa/testfunctionality").then().body(is("OK"));
    }

}

and everything worked as expected. I see from the logs that testcontainers is running before the Quarkus augmentation and application startup.

Ok, let me retry... I may have missed something (annotation order maybe?)

Ok, that was the issue, good to know!
I'm going to open a new issue to work on a better integration as the one mention by @bsideup

If it doesn't work I can try again, perhaps I have done something wrong on my side :)

@cescoffier Great!

Was this page helpful?
0 / 5 - 0 ratings