Testing multiple services
This page describes how to configure more complex tests where you need more than one service and, potentially, multiple instances of a single service.
You can link multiple descriptors to a test or test class by using the @WithSvc
annotation. For each descriptor, Dockerunit creates a Service
. Each service by default has one replica, however, you can declare multiple replicas. For each replica, Dockerunit creates a ServiceInstance
.
Dockerunit implements service discovery using Consul. It is necessary to add a @WebHealthCheck
or a @TCPHealthCheck
annotation to each of your descriptors, otherwise service discovery will fail and your test will not be executed. When you run a test, the following happens:
- Descriptor classes are interpreted to create containers.
- Each container is wrapped into a
ServiceInstance
and instances are grouped byService
. Finally, services are grouped into aServiceContext
. - The
ServiceContext
is passed to the discovery provider (dockerunit-consul), which registers eachServiceInstance
into Consul and then verifies that all the expected instances are successfully registered andhealthy
. - The updated
ServiceContext
is made available to the test through theDockerUnitRule
class. - The test is executed.
- The
ServiceContext
is cleaned up (containers are de-registered from Consul and deleted).
Declaring and using multiple descriptors
Let’s see a concrete example where we have two services:
- A MySQL database.
- A Spring service that uses the MySQL database.
Here is how the MySQL descriptor would look like:
import com.github.dockerunit.core.annotation.*;
import com.github.dockerunit.discovery.consul.annotation.TCPHealthCheck;
@Svc(name="mysql-db", image="mysql:8.0.18") // Service name and mysql docker image values
@TCPHealthCheck(port=3306) // Checks that MySQL is running on 3306 during service discovery
// Mounts "src/test/resources/init.sql" inside the container so your schema is executed at startup
@Volume(host="init.sql", container="/docker-entrypoint-initdb.d/init.sql", useClasspath=true)
public class MySQLDescriptor {
}
And here is how the Spring service descriptor would look like:
import com.github.dockerunit.core.annotation.*;
import com.github.dockerunit.discovery.consul.annotation.WebHealthCheck;
@Svc(name="spring-svc", image="my-spring-image:latest")
@WebHealthCheck(endpoint="/health-check", port=8080)
@PublishPorts // Maps the container port 8080 to a random port on the Docker bridge
// Makes this service use Consul as DNS,
// so that it can find the db under the "mysql-db.service.consul" name
@UseConsulDns
public class SpringSvcDescriptor {
}
Before we look at how to use these descriptors, let’s clarify a couple of details:
- Both the
@TCPHealthCheck
and the@WebHealthCheck
always refer to the container port, regardless of how this is being mapped. The actual health check is executed by Consul which runs on the same network as the containers that are created by Dockerunit. Consul is aware about the IP address of each container. - We are not mapping the MySQL port using
@PublishPort
because our test is not going to directly talk to it. - We are placing a
@UseConsulDns
annotation only on theSpringSvcDescriptor
because this service is talking to MySQL and not vice versa. - We are mapping the
spring-svc
port 8080 to a random one because we want to create two replicas. If we were to map that port to a specific one, then we would have faced a port conflict and our test would have failed. It is worth noticing that this is not a problem if we don’t need to reach our service directly from the test (for instance, it is an internal service or we are running a proxy line nginx in front of our services). In that case, we can simply omit port mapping, as the clients of such service will be able to reach it using its Consul name (Consul performs DNS load balancing).
Finally, let’s see how we can combine these descriptors into a single test class.
import org.junit.*;
import com.github.dockerunit.core.*;
import com.github.dockerunit.core.annotation.WithSvc;
import com.github.dockerunit.junit4.DockerUnitRule;
import io.restassured.RestAssured;
// Use higher priority for MySQL, so that it runs before any service tries to connect
@WithSvc(svc=MySQLDescriptor.class, priority=10)
// Use lower priority and spin two replicas
@WithSvc(svc=SpringSvcDescriptor.class, priority=1, replicas=2)
public class MyIntegrationTest {
@Rule
public DockerUnitRule rule = new DockerUnitRule();
private ServiceContext context;
@Before
public void setup() {
context = DockerUnitRule.getDefaultServiceContext();
}
@Test
public void testSpringSvc() {
Service s = context.getService("spring-svc");
ServiceInstance si = s.getInstances().stream().findAny().get();
RestAssured
.given()
.baseUri("http://" + si.getIp() + ":" + si.getPort())
.when()
....
}
}
Need more to get started? Try looking at a full example.