Lights, camera, TESTS!

Vero Isoardi
Unagi
Published in
5 min readOct 4, 2021

What do Tom Hanks, Meryl Streep, Julia Roberts, and Brad Pitt have in common? They all, at one time or another, used stunt doubles for their performances.

Now, what does that have to do with programming? A lot, and in this article, I’ll tell you about it.

Ilustración en la que un sujeto se ve a si mismo reflejado en una pantalla

When we think of software development, we should almost by default think about automatic tests. And writing good, easy to read, and maintain tests is a fundamental part of any development.

We can find many resources to improve our tests, among which stand out the -wait for it- test doubles.

[Explaining the advantages of testing is beyond the scope of this article, but if you are interested in learning more, I suggest you check out this article by Lucas Hourquebie and this article by Agustín Serena.]

Test Doubles are particularly helpful when running unit tests as we want to verify that what we are testing is correct without relying on other parts of our application.

The term refers to a resource impersonating the original object, which occupies a real part of our system for a specific purpose. The difference is that these doubles are usually much easier, as, in most cases, we are only interested in having some of the characteristics of the original object.

Stunt doubles for extreme or naked scenes?

Actors have multiple doubles depending on the scene, and so do our systems, which also have several doubles depending on what we need to verify.

Within the term test doubles, we include doubles, fakes, stubs, mocks, and spies. It seems like I am speaking in a new language, right? 🤔.

We’ll see an example with RSpec in Ruby applying all of them to prove that a mailer works correctly.

Let’s suppose we have a User model and an Account model, a Signup class, which sends an email when the user registers, and a Signup mailer, which sends the email.

User model
Account model
Signup class
SignupMailer class

Doubles

A double resembles and replaces a real object of our application during tests’ execution. In addition to defining them as we desire, we can add validations so that they meet specific requirements. For example: for a person X to be a double of Hugh Jackman, he should at least physically resemble him. The same happens with tests, but with the advantage that we can modify the original Hugh, for example, to have red hair.

In RSpec, we can name the object and define what it returns to a specific method call using the double method.

Stubs

A stub defines the response we expect an object to give when calling a given method. We can specify the arguments and other necessary validations to simulate the task properly.

Continuing with the example, we have to do 3 things:

  1. Create the user, the account, and the mailer. These are objects that we need to test to verify that sending the email works. However, they are not relevant. In other words, it is the ideal moment to use doubles 🙌🏻.
  2. Define what response we expect SignupMailer to return when calling the signup method. Yes! It is the perfect time to use a stub 🥳.
  3. Define what we expect the SignupMailer’s deliver method to be called.

Then the example would look like this 👇🏻:

Signup spec — Part 1

First, we create the doubles of User and Account using stubs to obtain the instances of the classes. Secondly, we create the double of the Mailer, but this time, we specify what it will answer if the method is called.

Once we have all the necessary objects, the next step is to make the stub of the SignupMailer. We must define which methods are expected to be called, with which arguments, and what they should answer.

So… we´re not testing anything yet 🤷🏻‍♀️. For that, we´ll need the mocks.

PS: in my world stubs play the role of spectators. It doesn’t matter if you use a double or the original object; the answer remains the same.

Mocks

A mock, like a stub, defines the response an object should give when a method is called. The difference is that mocks expect the method call to occur and how it was defined. If the call doesn’t occur, or not as previously defined, or takes place more times than expected, the test fails.

Let’s continue completing the example:

Signup spec — Part 2

Two things happen here: first, we create the Signup object and call the method that will trigger everything we stubbed before, and second, we create the mocks to verify that what we expect is achieved. But we are missing something… the logger.

STOP 🚫
Before moving on to the logger, let’s make a mini breakpoint on the spies.

Finally, to continue with the associations: the mock could be the super mega fan viewer who knows all the theory, doesn’t miss a detail, and when the story doesn’t close, is the first to complain.

Spies

A spy is a stub that also stores information about how it was called. They are particularly convenient for testing different services, such as sending emails or calling various APIs, by verifying how many calls were made to them in a specific situation.

There are two ways to define them:

  1. By hand, where we first create a stub, second, we trigger the method call, and third, with a mock, we verify that the method was called as expected. This allows us to maintain the 4 phases of a test in order, which are: setup, exercise, verify, and teardown.
    This is what we did in the previous steps 🤯.
  2. Using the spy method. The solution would look like this 👇🏻:
Signup spec — Part 3

Fakes

A fake is a PORO (Plain Old Ruby Object) used in the object under test. It has a functional implementation, which seeks to fulfill the purpose for which they were created.

✅ Pro: you give it the behavior you want.

❌ Con: you have to implement it.

To complete the example, we are missing the loggers. We have to do 2 things: create the PORO and use it in the test.

FakeLogger class
Signup spec — Part 4 & last

And this is how by using tests doubles you verify that an email is sent correctly.

The ideal is to use stubs, mocks, and spies rather than fakes, but if the implementation is difficult to read and quite long, maybe it’s time to go for a fake.

But why use these resources?

  1. They make our tests more readable, simplifying the setup and making a clearer relationship between our subject under test and the other objects.
  2. They allow isolating the behavior to be tested, testing only the functionality we want to verify.
  3. They are time-saving in using services such as APIs or mailers, avoiding unnecessary calls with easily mockable responses.
  4. They allow controlling the responses to our subject under test, which prevents it from failing due to external causes.

You can find more information about available methods and practices in the RSpec Mocks documentation.

Did you know about this? I love to keep learning about testing (no matter when or where you read this), so use the comments to tell me what you think 🤗. And don’t forget to leave 👏🏻 if you liked the article.

--

--

Vero Isoardi
Unagi
Writer for

Desarrolladora Ruby on Rails en Unagi. Estudiante de sistemas y diseño multimedia. Súper curiosa. ¿Soy realmente yo si no estoy preguntando el porqué de todo?🤓