Demystifying Unit Tests

Demystifying Unit Tests

February 17, 2023

The idea for this post came up after I gave a presentation on unit tests at the company I work for. During the presentation, I did a live code in which I created an endpoint using the concepts I’ll cover here, in order to isolate the component. I would love to build an application right here on the blog, but doing a step-by-step might get a bit tedious. So I decided to summarize what was presented, with code examples for better understanding.

What is a unit test?

A unit test consists of testing the smallest part of the code we’re developing — in our case, our methods. It’s not very reliable on its own, because we only test our logic and don’t test the integration between the system’s components.

Some questions I see about unit tests are: how can I test only a part of the code when it depends on an interface or a specific class?

To achieve that level of isolation, we have some concepts we can use:

Stub

We create a test double with the expected results, so we can simulate our specific cases.

In this example I created a class called PessoaFisica:

With the help of Laravel’s Mockery library, we create a stub for the buscaDadosPorCpf method, with the expected return:

Mock

It has the same characteristics as a stub, but we can also make assertions about its behavior, ensuring that a method will or won’t be called.

In this example, in addition to defining an output for the method, we also want to know its behavior. In the test below, we pass the method times(1), which tells us the method must be called once:

Dummies

A dummy is an object we create in the test for which we don’t need to define a return or make assertions about behavior — it only serves to satisfy a parameter of a class or method.

In the figure below, we have a dependency on cep (zip code) in our PessoaFisica class. Even though we don’t use it in the method we’re going to test, it has an impact when we instantiate the class, because we must satisfy that dependency:

So we create the dummy to satisfy what’s required in the class’s constructor: $cep = \Mockery::mock(Cep::class). With the dummy created, we can simply pass it in the constructor:

Spies

The goal of spies is to make assertions about a method call, instead of making assertions about the object’s behavior.

To illustrate, I’ll create a class called UsuarioService:

We can create a test to verify whether the criarUsuario method is called with the correct arguments:

With Mockery, we create a spy for the UsuarioService class. Then, the criarUsuario method is called with the arguments "Diego França" and "[email protected]". Finally, the shouldHaveReceived method is used to verify that the criarUsuario method was called once with the same arguments.

Fake

Fakes have the same functionality as a real class, but they’re used only in tests. An example of a fake is a database: we can create a method like salvar (save), but save only in memory (in an array). This way, we get the functionality of saving, but only at that moment.

To illustrate, I created a repository for saving a record to the database, but we don’t want our application to actually hit the database.

To build the fake, I’ll use an array where the data will be stored in the computer’s memory:

Conclusion

The first time you write a unit test, you might think that test serves no purpose, since many times we’re emulating generated data. But I’d like to share a personal experience: a unit test won’t guarantee your application won’t break — it only shows that your algorithm has no errors. That’s why it’s EXTREMELY important to apply other types of tests, like end to end and exploratory testing (not covered in this article), since those actually ensure the system as a whole works correctly.

Another tip for those just starting out and who want to write good tests is to follow this step-by-step that can serve as a baseline:

  • Create test data builders (to create input data for our tests).
  • Create our setup methods to avoid repeating code (tearDown and setUp).
  • Create tests with meaningful names.
  • Create good assertions.

All of these concepts are available in every programming language.

I hope this article can help you in some way. If you have any questions or critiques (always welcome), leave them in the comments.

💬 Comentários