Adore Me Adore Me
Adore Me

Adore Me

4.69

16 evaluări

TEST DOUBLE

31.03.2022

November 07, 2021 By adi

Test Double

When I started testing my code, every dependency of my system under test was a mock. I've gotten used to it since I found an example in a project I was working on and because the term was familiar to me (a few years before I worked as a web designer), I used it without looking for more information. In that project we used PHPUnit, but to be honest with you, I think at that time PHPUnit didn't even offer you another alternative like today (now you can create as well mocks and stubs). 

I heard about the test double concept in 2016, in an online article, 7 years after the publication of Gerard Meszaros' book: xUnit Test Patterns: Refactoring Test Code, and it was something revealing to me. Understanding each type of test doubles, took me a while, so I decided to write this article to make it a study resource for my colleagues.

Test Double

The Rock And His Stunt Double On The Set Of Jumanji: The Next Level

 

The name of this concept - test double - comes from cinematography. When making a movie, there are situations when the movie director needs to use a double for the main actor for doing some stunts because he doesn't want to endanger the actor's life when he has to jump through fire.

Just like The Rock looks almost identical with his stunt double, so does a dependency of the system under test look like his test double. Just as the film director must not endanger the life of an actor when he has to perform a stunt, so we must not send any email to a customer when we are testing that functionality.

According to Gerard Meszaros, there are five variations of test doubles: dummystubspymock, and fake, but Vladimir Khorikov, in his book: Unit Testing Principles, Practices, and Patterns, managed to group them all in two groups: mocks and stubs.

Mocks help to emulate and examine outcoming interactions. These interactions are calls made by the system under test to its dependencies which change their state.

Stubs help to emulate incoming interactions. These interactions are calls made by the system under test to its dependencies for getting data.

Mocks vs Stubs

Unit Testing Principles, Practices, and Patterns, Figure 5.2.

 

Sending an email is an outcoming interaction: an interaction that results in a side effect in the SMTP server. A test double emulating such an interaction is a mock. Retrieving data from the database is an incoming interaction, and it doesn’t result in an aside effect. The corresponding test double is a stub.

MOCKS

Both mocks and spies have the same role, the only distinction is that spies are manually written, whereas mocks are created with the help of a testing framework. I will choose a simple example where I will try to test if our system sends a welcome email when a new user is created.

I will start using two mocks, one for the repository and one for the mailer service. Because my business logic is inside a command handler, I can't do any assertions, but I will verify that MailerInterface::send() is called once.

The same test can be written using a spy for the mailer service. A great benefit is that I can do assertions on how many emails were sent and inspect the emails that were sent. The downside is that I will have to create a new class and I will have to maintain it for the rest of her life.

STUBS

I will start with dummy because is a simple, hardcoded value such as a null value, a mixed value (integer, string, boolean, etc), or a stdClass instance. In practical use, they are used when a parameter is needed for the tested method but without actually needing to use that parameter. With PHP moving towards type hints and return types being used more and more, dummies are of limited use because creating a dependency without a type will likely cause PHP to complain. If you are using older codebases where types are not being used, you can create a dummy by using PHP’s built-in stdClass object.

Both stubs and fakes have the same role, the only distinction is that fakes are manually written, whereas stubs are created with the help of a testing framework. As you can see, they have the same differences as those between mocks and spies. I will choose a simple example where I try to test if our system detects if there is an existing user when we are trying to create a new one with the same email address.

I will start using a stub for the repository. Because my business logic inside the command handle throws an exception when it found an existing user with the same email address, I will verify that exception is thrown.

The same test can be written using a fake repository. Even If I replaced the stub with a fake, I need to have the same expectation, because, even if I could, I wouldn't have been allowed to do assertions with stubs/fakes. You will see below why.

But there are a lot of benefits to using a fake instead of a stub:

  • live with a "production" code (the fake is coupled via interface)
  • is not limited by any testing framework or library
  • could be used to mitigate problems related to mocking and to make tests more readable and shorter

When I noticed the differences between mocks and stubs, aside from the outcoming and incoming interactions, I said that mocks help emulate and examine interactions between the system under test (SUT) and its dependencies, while stubs only help to emulate those interactions, not examine them. This is an important distinction. A call from the SUT to a stub/fake is not a part of the result the SUT produces. Such calls are used to provide inputs from which the SUT then generates the output. Asserting interactions with stubs is a common anti-pattern that leads to fragile tests, said Vladimir Khorikov in his book. To examine the interactions means to use the implementation details to do assertions. But if we do this, any refactorization that we can do in the future will force us to rewrite our tests (hence the fact that they become fragile).

 

See the following example:

To verify if the generation of this kind of report was successful, I assert that the number of users from the report (which is an implementation detail) is 1, which makes my test fragile. If in near future I want to remove that detail from the report (the number of users), I need to rethink my test. A better version of the above test should look like this:

I want to stop here with this article because I already covered all the main ideas and if I will continue, it will be too long. But I warmly recommend you to read the fifth chapter of the Unit Testing Principles, Practices, and Patterns book for finding how to combine these types of doubles

To make a summary of this article, you have to keep in mind these: 

  • the first group (mocks and spies) helps the SUT in outgoing interactions, and the second one (stubs and fakes) helps the SUT in incoming interactions
  • the differences between both groups are the same: mocks and stubs are created by the testing framework, and spies and fakes are manually written by you
  • in our world of typed PHP code, the dummy double will get upgraded to a stub

 

References

Unit Testing Principles, Practices, and Patterns