4

I implemented a POP3 server and client using javax.mail just to try doing integration testing with Docker. So I created two Docker images based on the openjdk:8-jre image and copied my jars to them and started it. Based on my configuration (see below) it is working. They are talking to each other.

But as want to have multiple integration tests it is going to be tedious to build an image for each one and starting them. Also I don't know how to automate the results. But then I stumbled across TestContainers and it seems that this would be big help when implementing those tests.

So I started to port those tests to TestContainers using my POP3 Server image as a GenericContainer and starting my POP3 Client classes in the JUnit test method. I exposed the port 24999 which my POP3 Server is listening to. But when I try to connect to the server I get the following error:

com.sun.mail.util.MailConnectException: Couldn't connect to host, port: localhost, 32782; timeout -1;
  nested exception is:
    java.net.ConnectException: Connection refused
...

There is probably some setting I am missing in TestContainers. Could you please help me.

Here is the code I am using:

public class DockerPop3AutocryptKeyProvidingAndReceivingTest {
    @Test
    public void test() throws InterruptedException {
        GenericContainer container = new GenericContainer<>("immerfroehlich/emailfilter:latest")
                .withExposedPorts(24999);
        
        container.start();
        
        String host = container.getContainerIpAddress();
        String port = container.getFirstMappedPort().toString();

        //The following is simplified, but copied from the working jar used in the Docker Client image/container
        MyPOP3Client client = new MyPOP3Client(host, port);
        client.connect();
        
        container.stop();
    }
}

This is how I create my Docker image:

FROM openjdk:8-jre

ADD build/distributions/MyPOP3Server.tar . #This is where I have packed all the needed files to. It gets unpacked by Docker.
#EXPOSE 24999 #I tried both with and without this expose
WORKDIR /MyPOP3Server/bin
ENTRYPOINT ["sh","MyPOP3Server"] #Executes the shell script which runs java with my jar

This is a simplified version of the code that is running inside the Server Jar:

MyPOP3Server server = new MyPOP3Server();
server.listenToPort(24999);

Please tell me what am I missing. What is wrong here?

Thanks and kind regards.

Jason Aller
  • 3,541
  • 28
  • 38
  • 38
NoradX
  • 53
  • 1
  • 1
  • 6
  • Can you make a breakpoint after the container starts and inspect it to see what port was mapped. Anyway if you do something like port = container.getMappedPort(24999) – MC Ninjava Oct 02 '19 at 13:45
  • OK, I changed to port = container.getMappedPort(24999) but it isn't working anyway. I did the debug, but I am not sure where to look at. container.exposedPorts is [24999]; container.portBindings is []. – NoradX Oct 02 '19 at 14:18

5 Answers5

4

There's some good advice in other answers; I'll augment these with a couple of other tips:

As already suggested:

  • absolutely do add the LogConsumer so that you can see the container's log output - maybe something useful will appear there, now or in the future. It's always good to have.

  • set a breakpoint after the container is started, just before you start your client.

Additionally, I'd hope the following things make the difference. While paused at the breakpoint:

  • run docker ps -a in the terminal
  • firstly, check that your container is running and hasn't exited. If it has exited, have a look at the logs for the container from the terminal.
  • secondly, check the port mapping in the docker ps output. You should see something like 0.0.0.0:32768->24999/tcp (first port number is random, though).
  • evaluate container.getFirstMappedPort() in your IDE and check that the port number you get back is the same as the randomised exposed port. Unless you have a very unusual Docker installation on your local machine, this container should be reachable at localhost: + this port.
  • if you've got this far then it's likely that there's something wrong with either the container or the client code. You could try connecting a different client to the running container - even something like nc could help if you don't have another POP3 client handy.

Another thing to try is to run the container manually, just to reduce the amount of indirection taking place. The Testcontainers code snippet you've given is equivalent to:

docker run -p 24999 immerfroehlich/emailfilter:latest

You might find that this helps you divide up the problem space into smaller pieces.

Richard North
  • 432
  • 4
  • 6
  • 1
    Thank you for your suggestions. It helped a lot in finding the right solution. I posted it as a new answer. Thanks. – NoradX Oct 20 '19 at 21:14
  • This approach helped me out as well. I took a step back, commented out other containers and read up about every used testcontainers method. The issue was, that I mapped to the wrong container port (.withExposedPorts()). – Oskar Westmeijer Feb 04 '23 at 13:07
3

Try adding a http check.

 new GenericContainer<>("immerfroehlich/emailfilter:latest")
 .withExposedPorts(24999)
 .waitingFor(new HttpWaitStrategy().forPort(24999)
 .withStartupTimeout(Duration.ofMinutes(5)));

There is a possibility that your container starts but you are trying to connect before your server initializes.

Also, register a log appender to see what is going on with the server inside the container.

 .withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger(
              DockerPop3AutocryptKeyProvidingAndReceivingTest.class)))
lecandas
  • 250
  • 4
  • 11
  • I'd definitely second use of the LogConsumer, but given that this is a POP3 server I suspect that the `HttpWaitStrategy` will not help, unfortunately. – Richard North Oct 08 '19 at 19:42
  • 1
    Hi, instead of HttpWaitStrategy I used a LogMessageWaitStrategy and added a System.out.println in the Server. In conjunction with the switch container.getMappedPort(24999) it has been the solution. Thank you. – NoradX Oct 20 '19 at 21:12
1

Thanks for all of your help. This led me towards the solution. It has been a combination of the missing WaitStrategy and problems with the port mapping.

Here is what I did: 1) In the MyPop3Server.listenToPort(String port) method I added a System.out.println:

public class MyPop3Server {
  public void listenToPort(String port) {
     //simplified: do initialization and listenToPort
     System.out.println("Awaiting Connection...");
  }
}

In my test I added a LogMessageWaitStrategy that listens for "Awaiting Connection"

GenericContainer container = new GenericContainer<>("immerfroehlich/emailfilter:latest")
   .waitingFor(Wait.forLogMessage("Awaiting Connection.*", 1))
   .withExposedPorts(24999);

2) I switched from container.getFirstMappedPort() to

container.getMappedPort(24999);

Here is the whole changed and working test code:

public class DockerPop3AutocryptKeyProvidingAndReceivingTest {
    @Test
    public void test() throws InterruptedException {
        GenericContainer container = new GenericContainer<>("immerfroehlich/emailfilter:latest")
                .waitingFor(Wait.forLogMessage("Awaiting Connection.*", 1))
                .withExposedPorts(24999);

        container.start();

        String host = container.getContainerIpAddress();
        String port = container.getMappedPort(24999).toString();

        //The following is simplified, but copied from the working jar used in the Docker Client image/container
        MyPOP3Client client = new MyPOP3Client(host, port);
        client.connect();

        container.stop();
    }
}

Thank you everyone.

NoradX
  • 53
  • 1
  • 1
  • 6
0

Since your mail server and client are running in the container, I think you should connect to port 24999 and not to the mapped port

Ashis Roy
  • 44
  • 4
  • No this is what I initially did. I just wanted to mention it so that noone asks is your Server working. Currently the Server is running inside the container and the client is running locally inside the JUnit test. – NoradX Oct 02 '19 at 14:13
0

Try container.getMappedPort(24999) instead getFirstMappedPort. Probably your docker image exposes a couple of ports.

ygun
  • 95
  • 1
  • 1
  • 9
  • As I already mentioned to MC Ninjava. I tried that but it doesn't do the trick. – NoradX Oct 02 '19 at 17:19
  • If you make a breakpoint before client connect, can you connect to the mapped port using telnet or another mail client? – ygun Oct 02 '19 at 17:24
  • No, it also didn't work. But then I started the container with Docker on the command line and it also didn't work. Then I read about networks in the docker manual and I got an idea. So two containers can talk to each other, but the host cannot talk to the container. In the manual it says the bridge network is the default network. What does it do? It allowes two containers to talk to each other (and maybe more). So I read about the host network. And it basically says on Linux it is on the same network as the host. ... – NoradX Oct 03 '19 at 08:40
  • ... I started the container directly with "docker run -i -t --network host immerfroehlich/emailfilter:latest" and tada I can connect to the container with JUnit Test. So the only thing I have to know now is, how to set the host network in TestContainers. I tried with "Network network = Network.builder().driver("host").build(); container.withNetwork(network)". But then I get a "ContainerLaunchException: container startup failed" and further below "DockerException: only one instance of host network is allowed." So does anyone know how to do this in TestContainers? – NoradX Oct 03 '19 at 08:41
  • Actually TestContainers is designed to work in bridge mode. It uses a randomized mapped port to ensure that your dockerized tests can run simultaneously. The other thing you can try is to insert some waiting condition (like `new GenericContainer("blah").waitingFor(Wait.forHttp("/some/url"))`) or even use timeout. – ygun Oct 03 '19 at 09:18
  • If you prefer to stick with host mode there is another builder method: `new GenericContainer("blabla").withNetworkMode("host")`. You have to set a network or network mode before container start. – ygun Oct 03 '19 at 09:21
  • Hi, just to correct my writings. To use the container.getMappedPort(24999) method helped. But I didn't realize it, because I didn't wait long enough in the breakpoint before doing the client connect. In conjunction with a LogMessageWaitStrategy it did the trick. Thank you. – NoradX Oct 20 '19 at 21:03