Integration testing with Testcontainers + Quarkus + MongoDB
Asked Answered
M

5

10

Trying out testcontainers for integration testing. I am testing rest api endpoint. Here is the technology stack - quarkus, RESTEasy and mongodb-client

I am able to see MongoDB container is started successfully but getting exception. Exception: "com.mongodb.MongoSocketOpenException: Exception opening socket"

2020-04-26 15:13:18,330 INFO  [org.tes.doc.DockerClientProviderStrategy] (main) Loaded org.testcontainers.dockerclient.UnixSocketClientProviderStrategy from ~/.testcontainers.properties, will try it first
2020-04-26 15:13:19,109 INFO  [org.tes.doc.UnixSocketClientProviderStrategy] (main) Accessing docker with local Unix socket
2020-04-26 15:13:19,109 INFO  [org.tes.doc.DockerClientProviderStrategy] (main) Found Docker environment with local Unix socket (unix:///var/run/docker.sock)
2020-04-26 15:13:19,258 INFO  [org.tes.DockerClientFactory] (main) Docker host IP address is localhost
2020-04-26 15:13:19,305 INFO  [org.tes.DockerClientFactory] (main) Connected to docker: 
  Server Version: 19.03.8
  API Version: 1.40
  Operating System: Docker Desktop
  Total Memory: 3940 MB
2020-04-26 15:13:19,524 INFO  [org.tes.uti.RegistryAuthLocator] (main) Credential helper/store (docker-credential-desktop) does not have credentials for quay.io
2020-04-26 15:13:20,106 INFO  [org.tes.DockerClientFactory] (main) Ryuk started - will monitor and terminate Testcontainers containers on JVM exit
2020-04-26 15:13:20,107 INFO  [org.tes.DockerClientFactory] (main) Checking the system...
2020-04-26 15:13:20,107 INFO  [org.tes.DockerClientFactory] (main) ✔︎ Docker server version should be at least 1.6.0
2020-04-26 15:13:20,230 INFO  [org.tes.DockerClientFactory] (main) ✔︎ Docker environment should have more than 2GB free disk space
2020-04-26 15:13:20,291 INFO  [🐳 .2]] (main) Creating container for image: mongo:4.2
2020-04-26 15:13:20,420 INFO  [🐳 .2]] (main) Starting container with ID: d8d142bcdef8e2ebe9c09f171845deffcda503d47aa4893cd44e72d7067f0cdd
2020-04-26 15:13:20,756 INFO  [🐳 .2]] (main) Container mongo:4.2 is starting: d8d142bcdef8e2ebe9c09f171845deffcda503d47aa4893cd44e72d7067f0cdd
2020-04-26 15:13:22,035 INFO  [🐳 .2]] (main) Container mongo:4.2 started in PT3.721S
2020-04-26 15:13:24,390 INFO  [org.mon.dri.cluster] (main) Cluster created with settings {hosts=[127.0.0.1:27017], mode=SINGLE, requiredClusterType=UNKNOWN, serverSelectionTimeout='30000 ms', maxWaitQueueSize=500}
2020-04-26 15:13:24,453 INFO  [org.mon.dri.cluster] (main) Cluster created with settings {hosts=[127.0.0.1:27017], mode=SINGLE, requiredClusterType=UNKNOWN, serverSelectionTimeout='30000 ms', maxWaitQueueSize=500}
2020-04-26 15:13:24,453 INFO  [org.mon.dri.cluster] (cluster-ClusterId{value='5ea5dd542fb66c613dc74629', description='null'}-127.0.0.1:27017) Exception in monitor thread while connecting to server 127.0.0.1:27017: com.mongodb.MongoSocketOpenException: Exception opening socket
    at com.mongodb.internal.connection.SocketChannelStream.open(SocketChannelStream.java:63)
    at com.mongodb.internal.connection.InternalStreamConnection.open(InternalStreamConnection.java:126)
    at com.mongodb.internal.connection.DefaultServerMonitor$ServerMonitorRunnable.run(DefaultServerMonitor.java:117)
    at java.lang.Thread.run(Thread.java:748)
Caused by: java.net.ConnectException: Connection refused
    at sun.nio.ch.SocketChannelImpl.checkConnect(Native Method)
    at sun.nio.ch.SocketChannelImpl.finishConnect(SocketChannelImpl.java:714)
    at sun.nio.ch.SocketAdaptor.connect(SocketAdaptor.java:122)
    at com.mongodb.internal.connection.SocketStreamHelper.initialize(SocketStreamHelper.java:64)
    at com.mongodb.internal.connection.SocketChannelStream.initializeSocketChannel(SocketChannelStream.java:72)
    at com.mongodb.internal.connection.SocketChannelStream.open(SocketChannelStream.java:60)
    ... 3 more

If I use docker run then my test case works properly.

docker run -p 27017:27017 --name mongodb mongo:4.2

using testcontainer as mentioned @ https://www.testcontainers.org/quickstart/junit_5_quickstart/

@Container
    static GenericContainer mongodb = new GenericContainer<>("mongo:4.2").withExposedPorts(27017);
Muldoon answered 26/4, 2020 at 20:0 Comment(0)
L
4

I can't say for certain without seeing your test configuration, but I'm guessing that it works with docker run and not Testcontainers because docker run exposes a fixed port (always 27017) but Testcontainers will expose port 27017 as a random port (to avoid port conflicts on test machines).

To use Testcontainers with a Quarkus test, your tests must follow this flow:

  1. Start containers. This is necessary because the random exposed port for MongoDB can only be known after the container has been started.
  2. Obtain randomized ports from Testcontainers after containers are started, then set any test configuration properties that depend on container ports. For example:

    static GenericContainer mongodb = new GenericContainer<>("mongo:4.2").withExposedPorts(27017);
    static {
      mongodb.start();
      System.setProperty("quarkus.mongodb.connection-string",
                         "mongodb://" + mongodb.getContainerIpAddress() + ":" + mongodb.getFirstMappedPort());
    }
    
  3. Let Quarkus start. Since Quarkus does not support dynamic configuration, you must set the MongoDB port before Quarkus starts.
Lucarne answered 27/4, 2020 at 0:15 Comment(0)
S
3

As of version 2.0.0.Alpha1, Quarkus will automatically start MongoDB via testcontainers in dev and test mode if no Mongo configuration has been supplied.

See this for more information

Sanitary answered 10/6, 2021 at 7:14 Comment(0)
D
2

This solution works but Quakus provide a cleaner way of doing this check documentation. Just create a class implementing QuarkusTestResourceLifecycleManager

    public static class Initializer implements QuarkusTestResourceLifecycleManager {

        @Override
        public Map<String, String> start() {
            mongodb.start();
            return Maps.of("quarkus.mongodb.connection-string", "mongodb://" + mongodb.getContainerIpAddress() + ":" + mongodb.getFirstMappedPort());
        }


        @Override
        public void stop() {
            mongodb.stop();
        }
    }

Then annotate your test with @QuarkusTestResource(MyTestClass.Initializer.class)

Distinct answered 28/12, 2020 at 9:58 Comment(0)
A
1

Extending the @juan-rada answer with example (Quarkus + Mongo + Junit5)


import java.util.Map;
import org.apache.groovy.util.Maps;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.MongoDBContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.utility.DockerImageName;
import io.quarkus.test.common.QuarkusTestResource;
import io.quarkus.test.common.QuarkusTestResourceLifecycleManager;
import io.quarkus.test.junit.QuarkusTest;

@QuarkusTest
@Testcontainers
@QuarkusTestResource(FooIT.Initializer.class)
public class FooIT {

    public static class Initializer implements QuarkusTestResourceLifecycleManager {
        @Override
        public Map<String, String> start() {
            FooIT.mongoDBContainer.start();
            // the way to dynamically expose allocated port
            return Maps.of("quarkus.mongodb.connection-string", "mongodb://" + mongoDBContainer.getContainerIpAddress() + ":" + mongoDBContainer.getFirstMappedPort() + "/foo");
        }

        @Override
        public void stop() {
            FooIT.mongoDBContainer.stop();
        }
    }


    @Container
    public static final MongoDBContainer mongoDBContainer = new MongoDBContainer(DockerImageName.parse("mongo:latest"));

    @Test
    public void testFoo() {
       ...
    }

}
Astaire answered 21/3, 2021 at 9:59 Comment(0)
R
0

You can use in your tests the @QuarkusTestResource(MongoDBLifecycleManager.class).

And here we have the implementation of MongoDBLifecycleManager:

public class MongoDBLifecycleManager implements QuarkusTestResourceLifecycleManager {

    private MongoDBContainer mongoDBContainer = new MongoDBContainer(DockerImageName.parse("mongo:5.0.10"));

    @Override
    public Map<String, String> start() {
        mongoDBContainer.start();

        return Map.of(
                "quarkus.mongodb.connection-string", mongoDBContainer.getConnectionString(),
                "quarkus.mongodb.database", "testDB");
    }

    @Override
    public void stop() {
        mongoDBContainer.stop();
    }
}

It is important to notice that your tests will ignore what you defined in the application.properties as MongoDB connection parameters, and use what the method start returns:

Map.of("quarkus.mongodb.connection-string", mongoDBContainer.getConnectionString(),
       "quarkus.mongodb.database", "testDB");

You can have other MongoDB instances running in your local machine, with test containers you will run in a random port.

In your pom.xml you will need some like:

<dependency>
        <groupId>org.testcontainers</groupId>
            <artifactId>testcontainers</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.testcontainers</groupId>
            <artifactId>junit-jupiter</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.testcontainers</groupId>
            <artifactId>mongodb</artifactId>
            <scope>test</scope>
        </dependency>
Rhythmical answered 12/1, 2023 at 15:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.