Hexagonal, or onion, architectures are perfect for separating domain logic from access to that logic, and resources the logic needs.
But how to test such an architecture?
In this presentation, you'll see a pragmatic yet robust approach to testing such an architecture, with a live coding demo using a Spring Boot application.
We'll find a correct balance between unit tests and IT tests. We'll look at different tools for IT tests, and we'll also take a look at granularity of unit tests: do you *really* need to test every class separately?
5. Make Adapters thin
Are only for providing entry and exit points to and
from your application core
Should contain no business logic whatsoever
…Except maybe some simple (view/entity) mapping
Application core
Contains all business logic
Has no external / infra dependencies whatsoever
6. Application core can be tested lightning fast with
unit tests
No point testing adapters with unit tests
…So, test adapters by using IT tests to setup
infrastructure
21. Some (most) classes have very little complexity
public static OrderEntity toEntity(Order order) {
OrderEntity orderEntity = new OrderEntity();
orderEntity.setName(order.name());
orderEntity.setPrice(order.price());
orderEntity.setId(order.id());
orderEntity.setCustomer(order.customer());
return orderEntity;
}
25. Onion architecture helps to maximize fast tests for your business logic
Unit tests for application core, IT tests for adapters
Minimize context for IT tests with test slices*
Work towards loosely coupled tests for your application core
have similar (but less needed) support
and
*
Hi there! Still got some energy left after all those Deep Dive talks? Anyway, glad you came!
My name is Michel Schudel. I’m a software engineer working for Craftsmen, a consultancy company in the Netherlands.
As a result, I see new projects frequently. The first thing I always do is checkout the code and open the test folder. That’ll give you some real insight into the application structure.
Looks familiar? This normally says something about application structure. Too much layers depend on each other, IT tests are needed to test some complex business logic.
But more importantly, it says something about unclear separation of layers. Now to combat this, some archicture patterns have emerged.
(explain onion architecture in pieces)
Now, in order to explain an onion architecture, I’m going walk with you through it from the inside out.
Oni
So, let’s see how this translates to an actual application. (explain Spring Boot application structure)
(speaks for itself)
(speaks for itself)
But, I hear you say: “But IT tests take a long time!” because infrastructure has to start up, I have to start my whole application.
Really? Do you really need to start your whole application?
Because if you apply the onion principle and keep your adapters really small, you don’t NEED everything.
So, SpringBootTest is overused. It spins up the entire application. And generally takes a long time to start. Fortunetely, Spring has introduced the concept of Test Slice Annotations, which spin up only a small part of the application. You can see a couple of examples here, which I will demo. But for an exthaustive list, you can go to the Spring site. Or even build custom slice Annotations yourself.
This is a recap of the application structure. Now, in order to the test the presentation layer, you can use the slice “WebMvcTest”.
It only spins up the MVC framework. Of course, since you have a dependency on a service, you need to mock it out.
SWITCH TO DEMO
Next up: The rest client. We have a slice called “RestClientTest” for that.
It only spins up either an embedded Mock rest server, or a wiremock instance. I’ll show both how it’s done.
SWITCH TO DEMO
It only spins up either an database, or Testcontainers. I’ll show both how it’s done.
SWITCH TO DEMO
So let’s jump right into the demo! First up: controllers.
NOTE: probably scrap this, since it’s more of the same.
Still, you see this all the time. Loads of mocks, captors, injectmocks and the like.
Now, a lot of the time I see this. EVERYTHING is a component. Could be, but makes testing very hard.
Because you need to write a test for basically every class. When you restructure something in production code, you have to change all the tests as well. This are “tightly coupled tests”.
So, how do we combat this? First, there are a couple of things to realize:
Well, first of all, try and realize this: a unit test is not the same as a class test.
A unit test can *be* a class test, but not necessarily.
But let’s look at the typical structure of helpers. Normally they contain very little complex logic, and it’s just one code branch.
So, if you takes these two things in mind, maybe we can rethink some of our application core’s structure.
Now, a lot of the time I see this. EVERYTHING is a component. Could be, but makes testing very hard.
So what if we rethink the structure of the application core? What if we just instantiate all the helpers for a particular service with a normal constructor, instead of all these component definitions? Then you would end up with ACTUAL components, which are groups of classes belonging together to perform some business logic. Let’s call these UNITS.
Now, once you do that, you can define an actual UNIT test: a test that tests a unit as a whole. You’ll end up with MUCH less mocks, and refactoring becomes easier: you can shuffle around the different classes within your unit, without the need to refactor all your tests. We’ll call these “loosley coupled tests”.