tl;dr: It’s easy to build microservices test-driven and I’m gonna show you how.
Building a microservices system in a test-driven way can seem quite hard. Based on my talk at the Eclipsecon Europe 2017 I will give an overview on how to create a docker-based microservices system in a test-driven way and how to test every reasonable scope.
Disclaimer: I will not explain every tool I use, especially neither Git nor Intellij Idea nor Spring nor Gradle nor Docker nor Cassandra; but I will try to add some links so that you can read some additional information about the used technologies.
Let’s get started with an elementary example for tests.
Therefore please checkout the repository by typing the following into your shell:
git clone https://github.com/gunkelolaf/test_as_you_run.git
and checking out the first step:
git checkout step1
You will find this basic project setup:
The project is organized as a Gradle multi-project build. This version of the project only contains one sub-project, the CapitalizeService.
Let’s have a look at the parent build.gradle:
When we look at the build.gradle of the CapitalizationService we only find an empty file since no additional setup is required.
The CapitalizeService fulfills a very simple task:
It takes a string, and capitalizes the first character. The whole business logic for that can be found in the CapitalizeUtil class. It’s quite easy to write unit tests for this logic:
All these tests run on my computer in approximately 50 milliseconds so they are super fast and fit into the definition of unit tests.
The Server Tests
Since we have unit-tested our whole business logic, we can go on and integrate it into a server.
Please checkout the next step:
git checkout step2
HINT: If you want to list all files changed and added in the commit please run the following command in your shell:
git diff-tree — no-commit-id — name-only -r step2
Let’s have a look at the changed and added files. The root build.gradle file was extended with a buildscript dependency:
Heading for line 9, we see the Spring Boot Plugin added to the buildscript classpath. This configuration allows us to use the plugin in every sub-project.
The build.gradle file of the CapitalizeService project got its first line:
apply from: '../service.gradle'
Since gradle supports script plugins we can externalize configurations for special types of projects. Therefore, all necessary configurations for services are done in of the service.gradle file inside the root directory.
Now that all dependencies are in place, we can put our mind to the implementation:
The CapitalizeService has the CapitalizeApplication class, which is a normal Spring application start class.
The CapitalizeController is a Spring RestController providing one GET-endpoint with a path variable defining the string to capitalize. Calling the path /capitalize/hello, the result will be “Hello”.
The implementation is pretty much straight forward. So how do we test this?
Spring provides us with (at least) two possible testing setups:
The following code shows a snippet of the ServerMockTest class:
In line 2 we see that the WebMvcTest-annotation is added to the test.
Because of the annotation, our application without the server (but with every layer behind the server) will get started before the actual test methods run.
The MockMvc object that is autowired in lines 5–6 provides us with the opportunity to test our endpoint with an actual request without the overhead of starting the whole server.
All these tests run in approximately 75 milliseconds, so they are also very fast and I don’t feel stopped at all running them on a regular basis while developing the service. From my point of view it’s absolutely feasible to use them for test-driven development.
Now that we have tested our business logic with unit tests and our endpoint with Server-Mock-Tests, we’re ready to test the whole server.
Spring provides us with a very convenient way to do this:
Let’s have a look at the ServerStackTest class (or actually a snippet from it):
The Lines 2, 5, and 6 are the first we have a look at. The SpringBootTest annotation will setup our whole server before the tests will take place. Additionally, the TestRestTemplate object that is autowired in lines 5 and 6 allows us to sent actual requests against our server. As we can see in line 16, we interact with our server just like we interact with any other REST resource.
These tests run in approximately 170 milliseconds, which feels to slow for a test-driven develpment approach. Therefore I really need the tests of the smaller scopes, like the Server-Mock-Test, to feel comfortable while developing services.
Still, considering these tests are testing the whole server, the run-time is blazing fast.
These two possibilities for server tests give us very comfortable ways of testing our software; furthermore, we’re enabled to write our services test-driven with nearly no overhead.
Let’s Cross Some Borders
Testing the CapitalizeService was pretty easy, since the service has no dependencies to any other system. That’s nice, but not really even near to a real-world problem. So let’s go on with a service depending on a database in part 2 of this series.