What types of tests (unit, functional, integration) work best with both?
Are there specific situations where it is most obvious that it is worth using one or the other? Examples in any language are welcome.
For unit testing , mocks are the top choices and I also believe that fakes have their place. Commonly, in unit tests we are testing the behavior of a specific class and trying to ignore the dependencies of this class to the maximum.
In this act of "ignoring" your dependencies, we use the mock concept to only make these dependencies behave in a certain way for each test.
With Mock
Let's look at an example in Java, using Mockito . Let's say I want to test a CreditoService
service that depends on a ParametroService
and which queries a value called bonus
through this parameter class. In this case we are concerned with testing the implementation of CreditoService
and we do not care how ParametroService works:
ParametroService parametroService = mock(ParametroService.class);
when(parametroService.getBonus(any(Empresa.class))).thenReturn("10.00");
CreditoService creditoService = new CreditoService(parametroService);
In the example above, I simply told parametroService
to return "10.00" when querying bonus
independent of empresa
that the class receives as a parameter.
And the main difference that separates Mock from a Fake is that with Mocks you can check and confirm if certain actions occurred with that Mock, because all of these actions on top of the mock are recorded. In the previous example I created a mock and although I have defined an action expected by it, I did not check if it was actually called. So, I can also add a check that the bonus method of the parametroService has been called a certain number of times:
verify(parametroService, times(1)).getBonus(any(Empresa.class));
With Fake
In this same example, I could use a Fake
of ParametroService
. Let's say, depending on the value I get from empresa
, I'd like ParametroService
to do something different internally; any kind of logic on top of the company. For example, if the past company was an affiliate, it would not have a bonus. We can have something like this then:
ParametroService parametroService = new ParametroServiceFake();
CreditoService creditoService = new CreditoService(parametroService);
Being ParametroServiceFake
an implementation of the same interface ParametroService
used by the actual implementation:
class ParametroServiceFake implements ParametroService {
String getBonus(Empresa empresa) {
if (empresa.isFilial()) {
return "0";
}
return "10.00";
}
}
In integration and functional tests this concept still exists, but Fakes often reign.
For example, in an integration test I can create an in-memory bank by doing a Fake
of a repository class, in which the data to be inserted into a so-called database are stored in% internal% of Fake, which can even provide search methods for this "saved" data in the repository.
I can do the same also by making a Map
of a class used to simulate the behavior of a Fake
:
class WebServiceFake implements WebService {
String consultarNomeCliente(String cpf) {
if (cpf.equals("123.123.123-11"))
return "Augusto Moraes Filho";
return "";
}
}
As you can see, it is a functional implementation, however, far from the real one. But when you need to simulate certain behaviors of external systems, this technique comes in handy.
We can also use in integration tests or functional mocks. In Spring this possibility exists and is very accessible through WebService
, in which you can, in any integration / functional test, make a mock of any injectable class.
Which one or in what sense are they better or worse when there are changes in the system, that is, to maintain the tests?
In general, mocks give less maintenance, mainly because of the context they are inserted: local, unitary and isolated tests of the other tests.
Fakes require functional code implementation. If we have code implemented, we have maintenance . And the problem only happens when a @MockBean
has to be "aligned" with the data of another Fake
.
Let's take an example. Let's say I want to do an integration test that uses data from two different external systems. One has the person data and the other has the current account. Then we have the person "Eduardo", CPF "123.123.123-11" and the checking account "9876-0". So, I can have a Fake
for each system. So far so good.
Let's get into the problem now. If I search for Eduardo's CPF on the first system, he must come. As if I do a search for the CPF on the second system, to have your checking account. If, by chance, one of the two sides does not bring results when searching for the CPF, my test will fail. This in itself will make me need to worry that both Fakes have CPF with person and current account data and that no one in the future will "break" this connection between both that exists in the actual scenario of use.