We don't always encounter green field projects. Maybe you are already invested some time in using Docker Compose to spin up your test environment and are wondering how to get started from here?
Let's look into how Testcontainers can support you on this journey.
Dockerfile and docker-compose.yml
Let's assume we did start out with running our application as a Docker container as well, using the following, pretty standard, Dockerfile:
FROM openjdk:8-jre-alpine
RUN addgroup -S spring && adduser -S spring -G spring
USER spring:spring
ARG JAR_FILE=build/libs/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
We also need to make sure the Spring-Boot jar has been built:
./gradlewbootJar
Finally, we have a Docker Compose file, that automatically builds the app image and spins it up, together with all dependencies:
To run this rest, make sure the Docker Compose setup is running:
dockercomposeup
You can run the tests directly from the IDE.
Afterwards, you can stop the Docker Compose services again:
dockercomposedown-v
Migrating to DockerComposeContainer
In order to tightly integrate the lifecycle of our test environment with the lifecycle of our tests, we can already integrate Testcontainers and still make use of our existing docker-compose.yml:
You also need to add the @Testcontainers annotation to the test class, if you want the Testcontainers-JUnit-Jupiter extension to manage the container lifecycle (similar to how we did in step 8).
Finally, make sure to configure RestAssured to access the dynamic port exposed by Testcontainers:
Run the test from the IDE, it works! Note how you don't need to run docker compose before the test, or manually clean up the environment after.
Migrating to individual Testcontainers objects
Instead of defining the necessary services in the docker-compose.yml file, we will now declare them as Java objects. Furthermore, we make use of the Docker networking feature, so that we can hardcode connection URLs and leverage the Docker DNS features.
Testcontainers also allows to build images as part of the test execution and run the corresponding container. We will use this for our Spring-Boot application:
Notice that we can also use the dependsOn() method, to control the startup order of our containers. As compared to the dependsOn config in Docker Compose, this will fully utilize Testcontainers' WaitStrategy support, to ensure the applications in the container are in a ready-to-use state.
Don't forget to configure RestAssured accordingly to use the appContainer details:
From this point, is just a small step to move our setup back to a @SpringBootTest. But why would we want to do this? Using @SpringBootTest bring a couple quality-of-life improvements for us as developers, such as faster feedback cycles (we don't have to rebuild the whole application and the image) or much easier debugging of the Java process.
So let's make our test a @SpringBootTest again, by annotating it:
And now we can use the @DynamicPropertySource method to comfortably configure the Spring-Boot application to use the containerized service dependencies.