A Gradle project which runs Integration tests
after Unit tests
(only if they pass). Before running the Integration tests
, Gradle
brings up Docker
containers using Docker Compose
. After the Integration tests
finish stop the Docker
containers.
Learn how to:
- Separate Unit tests from Integration tests to fail fast
- Use WireMock during
Integration tests
- Leverage Docker Compose to bring up external dependencies during the
Integration test
phase
While most blog posts and articles are suggesting to use a separate sourceSet
, this example
leverages a naming pattern for the test classes which should be run as part of the Integration tests
.
The Gradle test
task excludes all test classes with the following pattern "**/*Integration*"
.
test {
exclude("**/*Integration*")
}
A new task of type Test
and with name integrationTest
has been added. It uses the same
test classes directory and classpath as the test
task. It specifically includes the test classes,
which have been excluded in the test
task. It should run after the test
task.
task<Test>("integrationTest") {
description = "Runs integration tests."
group = "verification"
testClassesDirs = sourceSets.test.get().output.classesDirs
classpath = sourceSets.test.get().runtimeClasspath
include("**/*Integration*")
shouldRunAfter("test")
}
This project uses JUnit5. Both tasks should use the Junit platform aka. JUnit5.
tasks.withType(Test::class.java) {
useJUnitPlatform()
}
The last step is to include the integrationTest
task as part of the check
task.
check {
dependsOn("integrationTest")
}
In the ExternalServiceConsumerTest
class just comment in the following line before running ./gradlew check
.
// Assertions.fail<Any>("Just because")
The test
task fails and the build does not execute the integrationTest
task. ✅
WireMock is a mock API server. There are different ways to use it and to define the mock endpoints and responses. Since we want to bring up the mock server with Docker Compose
, I define the mocks in JSON files. See the wiremock
folder and in there the sample-mock-endpoint.json
.
In the docker-compose.yml
I use a WireMock Docker image and map the wiremock
folder
to the container.
After you run docker-compose up
, you can request GET http://localhost:9999/some/thing
. ✅
I use a Gradle plugin to integrate Docker Compose into the Gradle build. The integrationTest
task has been modified to
run docker-compose up
before and docker-compose down
after the tests.
task<Test>("integrationTest") {
description = "Runs integration tests."
...
dependsOn("composeUp")
finalizedBy("composeDown")
}
See the logs of the Build and Test step of the related GitHub Actions workflow. ✅
It was a lot easier than I thought. WireMock seems like a great tool which can help to simulate different API scenarios. Even those, which might be rather rare. Thinking of long response times for example. Using the JSON format and running WireMock in a Docker container makes the setup fairly straight forward.
Plus obviously we can bring up more than just the API dependency. Also databases like Postgres or Redis for example.
I don't see the benefit of using multiple sourceSets
. Filtering the tests by matching a pattern in the classname seems like an easy approach.
For this example I have chosen a multi-module setup (with a single module) and the BuildSrc
folder, so there are a few open questions.
- How would it work if I had
Integration tests
in more than one module? Would the WireMock container have to go up and down multiple times? - What about test coverage reports? How does that work when I have multiple tests tasks? I already noticed in a different project, that Jacoco together with
Console Reporter
plugin don't work reliably.