When do I use Integration Tests?
You cannot test everything with unit tests, and it’s a bad idea in general to test everything with higher level tests. So, how to find what…
You cannot test everything with unit tests, and it’s a bad idea in general to test everything with higher level tests. So, how to find what to test with what?.
For me, an Integration Test is any test that requires in the setup of the test some kind of non-trivial infrastructure to help me to verify that my code is creating the desired side effect.
In computer science, an operation, function or expression is said to have a side effect if it modifies some state variable value(s) outside its local environment, which is to say if it has any observable effect other than its primary effect of returning a value to the invoker of the operation. Example side effects include modifying a non-local variable, modifying a static local variable, modifying a mutable argument passed by reference, performing I/O or calling other functions with side-effects. In the presence of side effects, a program’s behaviour may depend on history; that is, the order of evaluation matters.
wikipedia
Examples of non-trivial infrastructure:
An in memory database.
A Docker container with any image.
A framework to send rest messages to my controller under test.
The need to read or write in the console.
Examples of trivial infrastructures:
An in memory data structure that helps me to verify the side effect without hard integrations.
Integration tests are the simple tests that you can imagine when you build a system, and I like to start with them. It’s important to mark that I don’t test everything with Integration Tests I just start with them, they help me to push for having a design that helps me to create unit tests.
I like to evolve my design, so I prefer to delay design decisions until the Last Responsible Moment, I try to wait for that moment to design (I design while coding).
I usually start by creating a React component, or a rest controller, or any entry point artifact for the customer. Following my definition above, I need an integration test to validate that my component is producing the side effect that I want.
React Component
For example, if I have to create a React component, I will create a test to validate that my component is able to paint in the browser some text.
For doing this, let’s use react-testing library that is non-trivial infrastructure. Also, we will check that the text is there, so I’m creating an Integration Test.
If I’m creating an Integration Test for a component, then I tend to think that the component is part of my Infrastructure.
In my opinion, infrastructure is like a virus, anything that use infrastructure is converted in infrastructure.
I like to identify my infrastructure because I want to push for decoupling the infrastructure from the business domain (the code that is not producing side effects). The code that doesn’t produce side effects is pure logic, is like a pure mathematical function, with the same input values will generate the same output value, perfect for my unit tests.
And why to decoupling infrastructure from business logic, in my opinion because it’s a good idea to reduce the code that is not under your control.
If your code is using a library or a framework needs to follow the rules of that library or framework that you don’t control.
If the owner of that library decides to do a breaking change you have a problem, but also you have a problem if they decide to change the terms of their tool (remember Docker Desktop).
Decoupling your business logic from your infrastructure will allow changing that infrastructure without changing your business logic. You have limited the effect of coupling, when two things are coupled they will change together if one needs to change, if they are not coupled one can change without changing the other.
So if things that use anything marked as infrastructure is infrastructure, how can I create that business domain that requires to not be infrastructure?.
Business logic (unit tests)
One way of doing this is to limit what the infrastructure is doing. Following the React example, if my react component needs to call a backend service to retrieve some values to calculate something when the client clicks on a button. Then, my React component can delegate to the business logic what to calculate when the user clicks the button.
To avoid coupling my business logic with infrastructure, I will move that business logic to another class and I will avoid to use inside that class any reference to anything that is infrastructure.
So I have to inject my business logic inside the React Component and the React Component will define a callback that responds to the user interaction (a controller, again infrastructure).
The controller will call the business logic to obtain the response we are interested in, but the business logic will not answer in an infrastructure language, it will answer in the ubiquitous language. A language created by the team to help the team to understand the problem without any infrastructure influence, this language is discovered not predefined.
But then how my Integration Test can check that things are working as expected, here is where using test doubles will help me to delay the implementation of my business logic to the future.
I can create a stub or a mock of my business logic that responds correctly depending on the input parameters of my Integration Test.
Test doubles help me to identify the borders between the infrastructure and the business logic, they are not for free, this is why I just try to use them in these borders. They are also a good indicator of borders between these concepts.
Backend infrastructure
Let’s imagine that our business logic needs to call a backend service, as I said anything that use infrastructure is converted to infrastructure, so I need to decouple the pure business logic from the side effects that we need to use.
We want to create a thin layer that is infected by the infrastructure to allow our beautiful business logic to live without side effects (and to preserve the ubiquitous language), so we can easily test it through Unit Tests.
This moves us to use the Repository Design Pattern. In Hexagonal Architecture terms, a secondary adapter that interacts with our infrastructure and implements an interface that our business logic uses as a collaborator.
That thin layer will be again tested with Integration Tests that checks we use correctly the library, framework or thing that we use to create the desired side effect. In this case, to call the backend service.
But why not using a unit test for this?, why not mocking Axios for example to avoid creating Integration Tests and all these different concepts?.
“Don’t Mock What You Don’t Own”
Because If you mock a library that you don’t control you cannot be sure you are using it correctly, any mock is an assumption of how that library works, not how it really works. So the mock creates a contract that you are not validating at all.
Then in this case let’s fake the server, let’s define how our server is going to behave in our Integration Tests. This will force us to use correctly the library or framework to interact through HTTP.
You could think, but you are creating a fake of the server, that’s also an assumption, you are right, but this one is less risky.
I’m reducing the level of uncertainty, I know that my code works with that server response. So the pressure to avoid problems is to be sure that the response of the server will be that one in that case:
Contract Testing (if you have influence in the other’s service)
Monitoring/Alerting the interaction between the two services.
Big Integration Tests
Too much integration tests usually mean a poor domain. All our logic is inside the infrastructure, we have created an anemic model.
This is a good smell to start thinking how to move logic inside the domain (business logic) to reduce the complexity of our integration tests because that will reduce the complexity of our Infrastructure:
We want to create unit tests because we want to move as much logic as we can to our business logic (domain).
We want to create a thin infrastructure layer, simple integration tests.
Integration tests are crucial in my opinion to have a good design and being pretty sure things are going to work as expected, but:
We should not convert them in Integrated Tests, let’s maintain them simple.
We should not avoid Integration Tests, mocking things that we don’t own.


