The idea of this article is to explain a possible approach to test your services.
Disclaimer: this is nothing new! Is an approach already explained in multiple places, such as the Microservices Patterns book by Chris Richardson.
There are code examples in the open-source Hexagonal Architecture Kotlin template project or another repository I use for an open-source-playground project. The silly example provided is, of course, tested (using Outside-In TDD).
This project is following the typical Testing Pyramid, but adapted to (Micro)services.
You will see that there are three different types of tests and each one of them has a concrete scope. I said three because we will not go into details with End-to-End tests.
End To End Tests
Is easy, in a microservices architecture normally the scope of e2e is broader than one service.
As I said, I will skip this kind of tests, let’s focus on one service scope testing.
Overview of a (Micro) service
The pic that you see represents a microservice with its components, following Ports & Adapters architecture (Aka Hexagonal) with some tactical patterns for DDD:
- Controller: receiving some http requests. Using Spring Boot for instance.
- Consumer of a stream (kafka or whatever).
- Use case aka application service: the orchestrator / api of our Domain.
- Domain: an Aggregate that contains an Entity an Value Objects.
- Repository: writing / reading from a database.
- Producer: publishing messages to a stream.
Component Tests
This is a kind of test that has been popularized by Microservices Patterns book (See Service Component Tests) and by, why not, Martin Fowler (see definition of Component Testing).
To understand component testing better, let’s start with the definition of a Component by Martin Fowler:
A component is any well-encapsulated, coherent and independently replaceable part of a larger system. In a microservice architecture, the components are the services themselves.
and then let’s take a look at Martin’s definition of component test:
A component test limits the scope of the exercised software to a portion of the system under test, manipulating the system through internal code interfaces and using test doubles to isolate the code under test from other components.
I interpret in this way: they are Acceptance Tests, but for a concrete service, instead of an entire Product (or Feature within a Product).
The scope is to test the service itself, in isolation, “mocking” external dependencies such as database (using TestContainers for instance) and external services (using Wiremock for instance).
So, the purpose of component testing is to check that the service solves the problem it was made to solve.
Here is an example of a component test I created:
Here is the link to the complete example.
Integration Tests
This kind of tests run in isolation, there is no call to an external service or a real database.
Here we include repositories, clients, controllers, consumers, producers tests etc. We can also include Contract Tests (extending Controller tests, for instance, to check contract with Consumers using some kind of tool like Pact.io).
The dependencies are “mocked”. What does it mean?
That the repository for instance, is using a mocked database, using for instance a Postgres docker container. Here is an example.
Now let’s have a look at an integration test for a controller:
and here the link to the complete example.
Unit Tests
Last but not least, we’ve Unit Tests!
We normally use them to test business logic (if any). Here we include all the tests for Aggregates, Value Objects, Entities, Domain Events, Application Services (aka Use Cases), Domain Services etc.
Here is an example of an unit test for an Application service / Use case:
and here the link to the complete example.
Conclusions
So, with Component, Integration and Unit tests, if done well, we’re pretty sure that we’re covering almost all the functional requirements (also some non-functional), the “connections” and wiring up with infrastructure (databases, queues, streams etc.) and what’s the most important, the business rules.
For component and integration tests, you may have noticed that is not super easy (that’s why they are in the upper layers of the pyramid). To make work Testcontainers, Wiremock and more stuff with Spring Boot is not super easy and sometimes can give headaches, but once done the first time, then for the next services is a child’s play.
Is it worth to create also Component Tests? Totally!
Some years ago I was skipping that part of the pyramid and I was delegating the responsibility to end-to-end tests, which are more flaky and expensive. Nowadays the tendency is the other way around: remove as much as e2e tests as possible, converting them into “service-scope” tests.
所有评论(0)