3

I've recently started using testcontantainers for unit/integration testing database operations in my Quarkus webapp. It works fine except I cannot figure out a way to dynamically set the MySQL port in the quarkus.datasource.url application property. Currently I'm using the deprecated withPortBindings method to force the containers to bind the exposed MySQL port to port 11111 but the right way is to let testcontainers pick a random one and override the quarkus.datasource.url property.

My unit test class

    @Testcontainers
    @QuarkusTest
    public class UserServiceTest {
      @Container
      private static final MySQLContainer MY_SQL_CONTAINER = (MySQLContainer) new MySQLContainer()
          .withDatabaseName("userServiceDb")
          .withUsername("foo")
          .withPassword("bar")
          .withUrlParam("serverTimezone", "UTC")
          .withExposedPorts(3306)
          .withCreateContainerCmdModifier(cmd ->
              ((CreateContainerCmd) cmd).withHostName("localhost")
                .withPortBindings(new PortBinding(Ports.Binding.bindPort(11111), new ExposedPort(3306)))  // deprecated, let testcontainers pick random free port
          );
      
      @BeforeAll
      public static void setup() {
        // TODO: use the return value from MY_SQL_CONTAINER.getJdbcUrl()
        // to set %test.quarkus.datasource.url
        LOGGER.info(" ********************** jdbc url = {}", MY_SQL_CONTAINER.getJdbcUrl());
      }
      // snip...
    }

my application.properties:

%test.quarkus.datasource.url=jdbc:mysql://localhost:11111/userServiceDb?serverTimezone=UTC
%test.quarkus.datasource.driver=com.mysql.cj.jdbc.Driver
%test.quarkus.datasource.username=foo
%test.quarkus.datasource.password=bar
%test.quarkus.hibernate-orm.dialect=org.hibernate.dialect.MySQL8Dialect

The Quarkus guide to configuring an app describes how to programmatically read an application property:

String databaseName = ConfigProvider.getConfig().getValue("database.name", String.class);

but not how to set it. This tutorial on using test containers with Quarkus implicates it should be possible:

// Below should not be used - Function is deprecated and for simplicity of test , You should override your properties at runtime

SOLUTION: As suggested in the accepted answer, I don't have to specify host and port in the datasource property. So the solution is to simply replace the two lines in application.properties:

%test.quarkus.datasource.url=jdbc:mysql://localhost:11111/userServiceDb
%test.quarkus.datasource.driver=com.mysql.cj.jdbc.Driver

with

%test.quarkus.datasource.url=jdbc:tc:mysql:///userServiceDb
%test.quarkus.datasource.driver=org.testcontainers.jdbc.ContainerDatabaseDriver

(and remove the unnecessary withExposedPorts and withCreateContainerCmdModifier method calls)

kosmičák
  • 1,043
  • 1
  • 17
  • 41

2 Answers2

3

Please read the documentation carefully. The port can be omitted.

https://www.testcontainers.org/modules/databases/jdbc/

Vitaly Chura
  • 704
  • 8
  • 13
2

now (quarkus version 19.03.12) it can be a bit simpler.

  1. Define test component that starts container and overrides JDBC props

import io.quarkus.test.common.QuarkusTestResourceLifecycleManager;
import org.testcontainers.containers.PostgreSQLContainer;

public class PostgresDatabaseResource implements QuarkusTestResourceLifecycleManager {

    public static final PostgreSQLContainer<?> DATABASE = new PostgreSQLContainer<>("postgres:10.5")
            .withDatabaseName("test_db")
            .withUsername("test_user")
            .withPassword("test_password")
            .withExposedPorts(5432);

    @Override
    public Map<String, String> start() {
        DATABASE.start();
        return Map.of(
                "quarkus.datasource.jdbc.url", DATABASE.getJdbcUrl(),
                "quarkus.datasource.db-kind", "postgresql",
                "quarkus.datasource.username", DATABASE.getUsername(),
                "quarkus.datasource.password", DATABASE.getPassword());
    }

    @Override
    public void stop() {
        DATABASE.stop();
    }
}
  1. use it in test
import io.quarkus.test.common.QuarkusTestResource;
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;

import javax.ws.rs.core.MediaType;
import java.util.UUID;
import java.util.stream.Collectors;

import static io.restassured.RestAssured.given;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;

@QuarkusTest
@QuarkusTestResource(PostgresDatabaseResource.class)
public class MyControllerTest {

    @Test
    public void myAwesomeControllerTestWithDb() {
      // whatever you want to test here. Quarkus will use Container DB
      given().contentType(MediaType.APPLICATION_JSON).body(blaBla)
                .when().post("/create-some-stuff").then()
                .statusCode(200).and()
                .extract()
                .body()
                .as(YourBean.class);
      
    }

Capacytron
  • 3,425
  • 6
  • 47
  • 80