|
2 | 2 |
|
3 | 3 | ## Benefits |
4 | 4 |
|
5 | | -Similar to generic containers support, it's also possible to run a bespoke set of services |
6 | | -specified in a `docker-compose.yml` file. |
| 5 | +Similar to generic container support, it's also possible to run a bespoke set of services specified in a |
| 6 | +`docker-compose.yml` file. |
7 | 7 |
|
8 | | -This is intended to be useful on projects where Docker Compose is already used in dev or other environments to define |
9 | | -services that an application may be dependent upon. |
| 8 | +This is especially useful for projects where Docker Compose is already used in development |
| 9 | +or other environments to define services that an application may be dependent upon. |
10 | 10 |
|
11 | | -Behind the scenes, Testcontainers actually launches a temporary Docker Compose client - in a container, of course, so |
12 | | -it's not necessary to have it installed on all developer/test machines. |
| 11 | +The `ComposeContainer` leverages [Compose V2](https://www.docker.com/blog/announcing-compose-v2-general-availability/), |
| 12 | +making it easy to use the same dependencies from the development environment within tests. |
13 | 13 |
|
14 | 14 | ## Example |
15 | 15 |
|
16 | | -A single class rule, pointing to a `docker-compose.yml` file, should be sufficient to launch any number of services |
17 | | -required by your tests: |
18 | | -```java |
19 | | -@ClassRule |
20 | | -public static DockerComposeContainer environment = |
21 | | - new DockerComposeContainer(new File("src/test/resources/compose-test.yml")) |
22 | | - .withExposedService("redis_1", REDIS_PORT) |
23 | | - .withExposedService("elasticsearch_1", ELASTICSEARCH_PORT); |
24 | | -``` |
| 16 | +A single class `ComposeContainer`, defined based on a `docker-compose.yml` file, |
| 17 | +should be sufficient to launch any number of services required by our tests: |
| 18 | + |
| 19 | +<!--codeinclude--> |
| 20 | +[Create a ComposeContainer](../../core/src/test/java/org/testcontainers/junit/ComposeContainerTest.java) inside_block:composeContainerConstructor |
| 21 | +<!--/codeinclude--> |
25 | 22 |
|
26 | | -In this example, `compose-test.yml` should have content such as: |
| 23 | +!!! note |
| 24 | + Make sure the service names use a `-` rather than `_` as separator. |
| 25 | + |
| 26 | +In this example, Docker Compose file should have content such as: |
27 | 27 | ```yaml |
28 | | -redis: |
29 | | - image: redis |
30 | | -elasticsearch: |
31 | | - image: elasticsearch |
| 28 | +services: |
| 29 | + redis: |
| 30 | + image: redis |
| 31 | + db: |
| 32 | + image: mysql:8.0.36 |
32 | 33 | ``` |
33 | 34 |
|
34 | | -Note that it is not necessary to define ports to be exposed in the YAML file; this would inhibit reuse/inclusion of the |
35 | | -file in other contexts. |
| 35 | +Note that it is not necessary to define ports to be exposed in the YAML file, |
| 36 | +as this would inhibit the reuse/inclusion of the file in other contexts. |
| 37 | +
|
| 38 | +Instead, Testcontainers will spin up a small `ambassador` container, |
| 39 | +which will proxy between the Compose-managed containers and ports that are accessible to our tests. |
36 | 40 |
|
37 | | -Instead, Testcontainers will spin up a small 'ambassador' container, which will proxy |
38 | | -between the Compose-managed containers and ports that are accessible to your tests. This is done using a separate, minimal |
39 | | -container that runs socat as a TCP proxy. |
| 41 | +## ComposeContainer vs DockerComposeContainer |
40 | 42 |
|
41 | | -## Accessing a container from tests |
| 43 | +So far, we discussed `ComposeContainer`, which supports docker compose [version 2](https://www.docker.com/blog/announcing-compose-v2-general-availability/). |
42 | 44 |
|
43 | | -The rule provides methods for discovering how your tests can interact with the containers: |
| 45 | +On the other hand, `DockerComposeContainer` utilizes Compose V1, which has been marked deprecated by Docker. |
| 46 | + |
| 47 | +The two APIs are quite similar, and most examples provided on this page can be applied to both of them. |
| 48 | + |
| 49 | +## Accessing a Container |
| 50 | + |
| 51 | +`ComposeContainer` provides methods for discovering how your tests can interact with the containers: |
44 | 52 |
|
45 | 53 | * `getServiceHost(serviceName, servicePort)` returns the IP address where the container is listening (via an ambassador |
46 | 54 | container) |
47 | 55 | * `getServicePort(serviceName, servicePort)` returns the Docker mapped port for a port that has been exposed (via an |
48 | 56 | ambassador container) |
49 | 57 |
|
50 | | -For example, with the Redis example above, the following will allow your tests to access the Redis service: |
51 | | -```java |
52 | | -String redisUrl = environment.getServiceHost("redis_1", REDIS_PORT) |
53 | | - + ":" + |
54 | | - environment.getServicePort("redis_1", REDIS_PORT); |
55 | | -``` |
| 58 | +Let's use this API to create the URL that will enable our tests to access the Redis service: |
| 59 | +<!--codeinclude--> |
| 60 | +[Access a Service's host and port](../../core/src/test/java/org/testcontainers/junit/ComposeContainerTest.java) inside_block:getServiceHostAndPort |
| 61 | +<!--/codeinclude--> |
56 | 62 |
|
57 | | -## Startup timeout |
| 63 | +## Wait Strategies and Startup Timeouts |
58 | 64 | Ordinarily Testcontainers will wait for up to 60 seconds for each exposed container's first mapped network port to start listening. |
59 | | - |
60 | 65 | This simple measure provides a basic check whether a container is ready for use. |
61 | 66 |
|
62 | | -There are overloaded `withExposedService` methods that take a `WaitStrategy` so you can specify a timeout strategy per container. |
| 67 | +There are overloaded `withExposedService` methods that take a `WaitStrategy` |
| 68 | +where we can specify a timeout strategy per container. |
63 | 69 |
|
64 | | -### Waiting for startup examples |
| 70 | +We can either use the fluent API to crate a [custom strategy](../features/startup_and_waits.md) or use one of the already existing ones, |
| 71 | +accessible via the static factory methods from of the `Wait` class. |
65 | 72 |
|
66 | | -Waiting for exposed port to start listening: |
67 | | -```java |
68 | | -@ClassRule |
69 | | -public static DockerComposeContainer environment = |
70 | | - new DockerComposeContainer(new File("src/test/resources/compose-test.yml")) |
71 | | - .withExposedService("redis_1", REDIS_PORT, |
72 | | - Wait.forListeningPort().withStartupTimeout(Duration.ofSeconds(30))); |
73 | | -``` |
| 73 | +For instance, we can wait for exposed port and set a custom timeout: |
| 74 | +<!--codeinclude--> |
| 75 | +[Wait for the exposed port and use a custom timeout](../../core/src/test/java/org/testcontainers/junit/ComposeContainerWithWaitStrategies.java) inside_block:composeContainerWaitForPortWithTimeout |
| 76 | +<!--/codeinclude--> |
74 | 77 |
|
75 | | -Wait for arbitrary status codes on an HTTPS endpoint: |
76 | | -```java |
77 | | -@ClassRule |
78 | | -public static DockerComposeContainer environment = |
79 | | - new DockerComposeContainer(new File("src/test/resources/compose-test.yml")) |
80 | | - .withExposedService("elasticsearch_1", ELASTICSEARCH_PORT, |
81 | | - Wait.forHttp("/all") |
82 | | - .forStatusCode(200) |
83 | | - .forStatusCode(401) |
84 | | - .usingTls()); |
85 | | -``` |
| 78 | +Needless to say, we can define different strategies for each service in our Docker Compose setup. |
86 | 79 |
|
87 | | -Separate wait strategies for each container: |
88 | | -```java |
89 | | -@ClassRule |
90 | | -public static DockerComposeContainer environment = |
91 | | - new DockerComposeContainer(new File("src/test/resources/compose-test.yml")) |
92 | | - .withExposedService("redis_1", REDIS_PORT, Wait.forListeningPort()) |
93 | | - .withExposedService("elasticsearch_1", ELASTICSEARCH_PORT, |
94 | | - Wait.forHttp("/all") |
95 | | - .forStatusCode(200) |
96 | | - .forStatusCode(401) |
97 | | - .usingTls()); |
98 | | -``` |
| 80 | +For example, our Redis container can wait for a successful redis-cli command, |
| 81 | +while our db service waits for a specific log message: |
99 | 82 |
|
100 | | -Alternatively, you can use `waitingFor(serviceName, waitStrategy)`, |
101 | | -for example if you need to wait on a log message from a service, but don't need to expose a port. |
| 83 | +<!--codeinclude--> |
| 84 | +[Wait for a custom command and a log message](../../core/src/test/java/org/testcontainers/junit/ComposeContainerWithWaitStrategies.java) inside_block:composeContainerWithCombinedWaitStrategies |
| 85 | +<!--/codeinclude--> |
102 | 86 |
|
103 | | -```java |
104 | | -@ClassRule |
105 | | -public static DockerComposeContainer environment = |
106 | | - new DockerComposeContainer(new File("src/test/resources/compose-test.yml")) |
107 | | - .withExposedService("redis_1", REDIS_PORT, Wait.forListeningPort()) |
108 | | - .waitingFor("db_1", Wait.forLogMessage("started", 1)); |
109 | | -``` |
110 | 87 |
|
111 | | -## 'Local compose' mode |
112 | 88 |
|
113 | | -You can override Testcontainers' default behaviour and make it use a `docker-compose` binary installed on the local machine. |
114 | | -This will generally yield an experience that is closer to running docker-compose locally, with the caveat that Docker Compose needs to be present on dev and CI machines. |
115 | | -```java |
116 | | -public static DockerComposeContainer environment = |
117 | | - new DockerComposeContainer(new File("src/test/resources/compose-test.yml")) |
118 | | - .withExposedService("redis_1", REDIS_PORT, Wait.forListeningPort()) |
119 | | - .waitingFor("db_1", Wait.forLogMessage("started", 1)) |
120 | | - .withLocalCompose(true); |
121 | | -``` |
| 89 | +## The 'Local Compose' Mode |
122 | 90 |
|
123 | | -## Compose V2 |
| 91 | +We can override Testcontainers' default behaviour and make it use a `docker-compose` binary installed on the local machine. |
124 | 92 |
|
125 | | -[Compose V2 is GA](https://www.docker.com/blog/announcing-compose-v2-general-availability/) and it relies on the `docker` command itself instead of `docker-compose`. |
126 | | -Testcontainers provides `ComposeContainer` if you want to use Compose V2. |
| 93 | +This will generally yield an experience that is closer to running _docker compose_ locally, |
| 94 | +with the caveat that Docker Compose needs to be present on dev and CI machines. |
127 | 95 |
|
128 | | -```java |
129 | | -public static ComposeContainer environment = |
130 | | - new ComposeContainer(new File("src/test/resources/compose-test.yml")) |
131 | | - .withExposedService("redis-1", REDIS_PORT, Wait.forListeningPort()) |
132 | | - .waitingFor("db-1", Wait.forLogMessage("started", 1)); |
133 | | -``` |
134 | | - |
135 | | -!!! note |
136 | | - Make sure the service name use a `-` instead of `_` as separator using `ComposeContainer`. |
| 96 | +<!--codeinclude--> |
| 97 | +[Use ComposeContainer in 'Local Compose' mode](../../core/src/test/java/org/testcontainers/containers/ComposeProfilesOptionTest.java) inside_block:composeContainerWithLocalCompose |
| 98 | +<!--/codeinclude--> |
137 | 99 |
|
138 | | -## Build working directory |
| 100 | +## Build Working Directory |
139 | 101 |
|
140 | | -You can select what files should be copied only via `withCopyFilesInContainer`: |
| 102 | +We can select what files should be copied only via `withCopyFilesInContainer`: |
141 | 103 |
|
142 | | -```java |
143 | | -public static ComposeContainer environment = |
144 | | - new ComposeContainer(new File("compose.yml")) |
145 | | - .withCopyFilesInContainer(".env"); |
146 | | -``` |
| 104 | +<!--codeinclude--> |
| 105 | +[Use ComposeContainer in 'Local Compose' mode](../../core/src/test/java/org/testcontainers/junit/ComposeContainerWithCopyFilesTest.java) inside_block:composeContainerWithCopyFiles |
| 106 | +<!--/codeinclude--> |
147 | 107 |
|
148 | | -In this example, only `compose.yml` and `.env` are copied over into the container that will run the Docker Compose file. |
| 108 | +In this example, only docker compose and env files are copied over into the container that will run the Docker Compose file. |
149 | 109 | By default, all files in the same directory as the compose file are copied over. |
150 | 110 |
|
151 | | -This can be used with `DockerComposeContainer` and `ComposeContainer`. |
152 | | -You can use file and directory references. |
| 111 | +We can use file and directory references. |
153 | 112 | They are always resolved relative to the directory where the compose file resides. |
154 | 113 |
|
155 | 114 | !!! note |
156 | | - This only work with containarized Compose, not with `Local Compose` mode. |
| 115 | + This can be used with `DockerComposeContainer` and `ComposeContainer`, but **only in the containerized Compose (not with `Local Compose` mode)**. |
157 | 116 |
|
158 | 117 | ## Using private repositories in Docker compose |
159 | | -When Docker Compose is used in container mode (not local), it's needs to be made aware of Docker settings for private repositories. |
| 118 | +When Docker Compose is used in container mode (not local), it needs to be made aware of Docker |
| 119 | +settings for private repositories. |
160 | 120 | By default, those setting are located in `$HOME/.docker/config.json`. |
161 | 121 |
|
162 | 122 | There are 3 ways to specify location of the `config.json` for Docker Compose: |
|
0 commit comments