More Effective Testing of Spring Microservices
In this post we will look into using port level tests to make microservice testing experience faster/more pleasant. As a nice side effect we will also improve our application design.
This post contains some Spring specifics but the idea itself should be applicable to any similar framework.
Test Categories
Having clear test categorisation with explicit guidelines is very valuable within a team/service. This way we can avoid amorphous tests that follow random patterns and are hard to read.
There are many ways how to categorise tests. The approach we have used in Send Money Quotes team at TransferWise in the past is having following test categories:
- unit test — typically focused test that executes either single class or a small set of related classes
- integration test —mostly DB integration but can also be integration with some external library e.g metrics
- component test —whole service executed through HTTP API in either mocked or embedded servlet mode (tests use the
@SpringBooTest
annotation)
We have also tried cross service e2e tests but have not found the benefits significant enough to justify the effort.
Component Tests
One of the problems with component tests is that they are slow. Or to be more precise running the first @SpringBootTest
is slow. Later ones are quite fast as long as they share the same application context.
There are two main benefits of component tests:
- they verify integration points with (Spring) framework (aop, mvc etc)
- they verify that our own code works when put together
The second aspect is interesting. If we want to make sure that our own code works then why should we test it together with the MVC framework?
Port Level Tests
Note that term “port” in this context has nothing to do with network port.
Typically the main Spring feature we might be using in our business layer is dependency injection. However, if we use JavaConfig we can start our whole business layer without any Spring dependencies.
Furthermore most of our services do not need to be Spring beans. Only the facades that are accessed from framework specific IO adapters need to be defined as beans. This means we can have JavaConfig like this:
GetCurrencyRoutes
is the facade for our application logic. Gateways are defined as beans as well so we can stub them.
Following the idea that most of our code should be framework agnostic (great example how to achieve that — Decoupling from Rails by Jim Weirich) then there is little value in testing our application logic through HTTP API . Of course we need a few tests to verify our API contract but these do not need to go deep into checking our application/business rules.
Some people propose that instead of writing small focused unit tests we should be testing our business logic through its facades. In hexagonal architecture these facades are called ports. Testing through them is then called port level testing.
I think unit tests are still useful for driving/testing the design of production code. However, if our primary goal is avoiding regression then perhaps indeed we are better off writing more port level tests and less unit tests.
Example Port Level Test
Port level test can be something like this:
It shares the exact same wiring as production code (via the Beans
). At the same time it has no dependency on Spring so executes very fast.
Test Categories Updated
As a result we end up with following categories:
- unit tests
- integration tests
- port level tests
- API contract tests
We still want some tests that execute our application code together with the framework. Fortunately, we need very few of these. When we run the whole suite then total execution time may not be that different. However, when working on a specific feature cutting off 8 seconds every time we run a test in the test/develop cycle is quite nice. As an added benefit this pushes us to decouple ourselves more from the framework which is always something to strive for.
Some other posts on this topic
Building Modular Apps Using Spring by me
Testing Strategies in Microservice Architecture by Toby Clemson
Optimizing Spring Integration Tests by José Carlos Valero Sánchez
P.S. Interested to join us? We’re hiring. Check out our open Engineering roles.