Unit tests
It is not easy to define borders between integration and unit tests. I would like to give my vision here.
It is not easy to define borders between integration and unit tests. I would like to give my vision here.

I wrote on this blog post about this topic but several people told me that it was unclear for them the unit concept. Before starting with it, I would like to differentiate unit and integration tests.
The biggest difference in my opinion is that unit tests usually do not need any kind of infrastructure to be executed. No need to start a database in memory for example. They only depend on the language and the test framework we are using. The second one for me is how easy is to find a bug in unit tests comparing them to integration tests.
They are few differences but they have big consequences:
Unit tests are several times faster than integration tests.
Unit tests are not coupled with anything else than your code, the code you wrote.
Unit tests tell us where is our bug.
Why is important the response time of your test suite?
Let’s now imagine two teams :
Team “Integration” with 600 integration tests in their codebase. Their integration tests are lasting at average 1 seconds per test.
Team “Unit” with 600 unit tests in their codebase. Their unit tests are lasting at average 0.1 seconds per test.
Team “Integration” will need to wait 10 minutes to just pass their tests, but team “Unit” will need just 1 minute. What do you think is the effect on the frequency of both teams to execute their test suites?
Yes, Team “Unit” will execute the tests more frequently than team “Integration”. And we know that with frequent and fast feedback the cost of fixing bugs is cheaper.
Unit tests can scale very well, going from 600 tests to 1200 tests in your system is not a big problem for your build time.
Test and coupling
On the other hand we said that unit test are not coupled to the infrastructure you need in your application. But why is this a problem?.
Coupling with a vendor in your application is like accepting their terms. Now depending on how important and big is that vendor you will be a client or a prisoner. Last years, software industry started adopting Clean Architectures to avoid these problems. But we didn’t realize that our tests are also there and are part of our codebase, something that is valid for our production code is also valid for our tests code.
If the majority of our tests are coupled with a third party library outside our control all our efforts to design a low coupled system are lost.
Our tests are the warranty that everything is still working, if they are coupled with the infrastructure then changing that infrastructure produce to change our tests. It’s very easy to introduce new bugs when we try to do a refactor and change our tests at the same time.
If we need to change tons of tests then the number of bugs introduced will be high.
This is not a big problem if the majority of your tests are unit tests.
So why the discussion between integration and unit tests?
Integration tests have a benefit over unit tests and this benefit is related to the fact that they are black box tests testing a behavior of your application with real components. This is giving the developer the possibility to refactor their code in the middle of the behavior without changing any test.
But wait, in reality this is something that unit test can achieve. The problem is the borders we select for our unit.
Unit and behavior
Let’s try to explain it with an example to show the problem.
Imagine that we want to develop a rest service in our Employees microservice to send employees Christmas gifts.
We are going to develop our new feature in this way:
Controller to send a Christmas gift to all our active employees.
Employees Service that retrieves all our active employees from our employees repository and for each one sends a gift.
Employees repository, this is in charge of talking with our employees database.
Gift repository, to send the gift to our employee. Another rest service to call.
This is the design that supports our behavior to test, now we have several options to test it.
We could create an integration test with an in memory database and with a rest mock server to simulate our infrastructure. In this case our test is coupled with the in memory database and with the rest mock server. For the rest mock server we will need to make assumptions of how it is working: which are the commands to use, the expected results, etc.
So we are high coupled to this third party system in the tests that are testing our Service.We can test our infrastructure (Controller, Employees Repository, Gift Repository) with Integration tests and our domain (Service) with unit tests. In this case our infrastructure is coupled with its corresponding technology in our tests but our domain is not coupled with our infrastructure in our tests.
Others
In the second case we are testing our domain with unit tests and those unit tests have clear borders, our repositories. Those repositories must be replaced by test doubles. But wait, test doubles are not for free, they are assuming how our real collaborators are going to behave. We are not going to talk about this but this is the starting point to go through Contract Tests.
The Controller would be tested with an integration test and the call to the EmployeesService would be mocked, in this way we are testing our infrastructure with our Integration tests but not with our domain. The same happens with our repositories, they would be tested with integration tests with no domain.
Now imagine that as a new requirement we need to send the gift’s price to our Tax system. At that moment we have realized that for calling our Tax System we have to do several complicated calculations, so we have created the Tax class that will do these calculations, now our Employees Service has:
Employees Repository.
Gift Repository.
Tax that will do some calculations before calling the Taxes Repository.
Impact in our tests
To introduce Tax in our design we have decided to include it as a collaborator and this decision will have an impact in our tests:
We could mock Tax for our tests in the Employees Service and add new tests to Tax.
We could try to test our Employees Service with the real Tax.
The effect of the first decision is to create a border between Tax and our Employees Service.
class EmployeeService{
private Tax tax;
...
public EmployeeService(Tax tax, ....){
this.tax = tax;
...
}
public void sendGifts(){
...
tax.informGift(a,b,c);
...
}
}To test EmployeeService following the first approach:
class EmployeeServiceTest { @Test
public void sendGifts(){
Tax tax= mock(Task.class);
when(tax.informGift(a,b,c)).thenReturn(expectedResult); EmployeeService service = new EmployeeService(tax, ...);
service.sendGifts(); //myAssertions
}
}To test EmployeeServiceTest.sendGifts we are making assumptions on how informGift is behaving (the when part), if those assumptions are incorrect then we will introduce bugs. Now if we change the method signature of Tax.informGift then we will have to change our test doubles, but this is also true for any of the assumptions done by my test doubles (whens).
In this way any refactor of the internal behavior of my domain is affecting a lot of parts in my tests. My tests are coupled to the Tax through the test doubles I’ve created. In someway the tests in the Employee Service pushes me to not refactoring anything inside Tax.
In the second option, to test EmployeeService:
class EmployeeServiceTest { @Test
public void sendGifts(){
Tax tax= new Tax(...) EmployeeService service = new EmployeeService(tax, ...);
service.sendGifts(); //myAssertions
}
}In this option we don’t have assumptions in the Employee Service tests about Tax. So my unit in this case is the behavior I want to test, EmployeeService and Tax both together. In this second option changing the signature or any assumption in Tax.informGift is not a problem for my unit tests. Tax.informGift is hidden to my test.
The risk in the second approach is to create an Integated Test, the effects of integrated tests are the explosion of test cases.
Explosion of tests
To explain it, imagine in our example that we need 5 tests to test Tax.informGift and other 5 tests to test the code in our EmployeeService.sendGifts. The number of tests to cover both parts though EmployeeService.informGift is 25 (5*5). This is the only reason to split our test in two, 5 for the EmployeeService and 5 for the Tax. So in EmployeeService.sendGifts tests we will mock Tax.informGift to avoid having this explosion.
When we start writing our tests we can always start with the second approach, but we just need to be aware of the explosion of tests. If we start seeing that explosion just go back to the first approach for that specific behavior to test. In reality this is only a description of the classical TDD approach. TDD does not say anything about unit and integration tests, the reason to test using unit tests or integration test is based on what I explained at the beginning of this article.
Unit tests are good for those things we can easily change (our domain). Integration tests are good for things that we cannot change (sql language for example). Let’s put them in the correct places.
So as a corollary, there is not only one unit in unit tests. It maps with each one of your higher behaviors your system defines that does not require any kind of infrastructure and does not produce an explosion of tests, this means the unit can be different between your behaviors.

