JavaScript testing #11. Spying on functions. Pitfalls of not resetting Jest mocks

0
29
JavaScript testing #11. Spying on functions. Pitfalls of not resetting Jest mocks


Jest offers a lot of functionalities for mocking functions and spying on them. Unfortunately, there are some pitfalls we need to watch out for. In this article, we explain how spying on functions works. We also explain how and why we should reset our mocks.

Creating mock functions

The most straightforward way of creating a mock function is to use the  method.

A mock function has a set of useful utilities that can come in handy in our tests. One of them is the function that allows us to define the implementation of our function.

There is also a shorter way to achieve the above by providing a function to the function.

Shorter variants of the mockImplementation function

Particular use-cases of the function can get pretty repetitive. Thankfully, Jest provides us with its shorter variants.

Returning a value

For example, we often want to create a mock function that returns a certain value.

We can simplify the above by using the function instead.

Returning a resolved promise

We often create mock functions that are supposed to return a promise that resolves to a particular value.

We can make the above more readable by using the function.

Returning a rejected promise

Also, we sometimes want our mock function to return a rejected promise.

We can make the above example simpler by using the function.

Spying on a mock function

To check if a function has been called, we can use the  function.

If we want to be more precise, we can use the and functions.

If we don’t care about one of the arguments of our function, we can use together with .

  matches anything besides and .

Getting more details about our mock

We can also access the details of the calls to our function through the property.

Through the property, we can get the results of all of the calls made to our mock function.

The property would contain if a call started, but didn’t complete yet. The  would contain if the call would complete by throwing a value.

The property contains all instances of objects created using our mock function and the keyword.

A real-life example of spying

To better grasp the idea of spying, let’s create a straightforward set of functions.

parsePeople.ts

The above function takes an array of people and performs various operations on the data.

person.ts

One of the operations performed by  is figuring out the youngest person.

getYoungestPerson.ts

The other operation is grouping the people by country.

groupPeopleByCountry.ts

Writing simple unit tests

Instead of writing unit tests for all of the above functions, we might want to write one integration test for the function. In some cases, though, we might want to prefer to write unit tests for each function separately. So let’s start by writing a straightforward test for the function.

groupPeopleByCountry.test.ts

Let’s also write an elementary unit test for the function.

getYoungestPerson.test.ts

Mocking certain functions when writing unit tests

Thanks to the above tests, we’ve got a lot of the code covered already. We still need to test the function. If we want to take a unit-testing approach, we can start by mocking and functions.

parsePeople.test.ts

If you want to know more about mocking modules, check outJavaScript testing #10. Advanced mocking with Jest and React Testing Library

Thanks to the above, when we call the function in our tests, Jest does not call the real and functions. Thanks to that, we can focus on testing the logic contained by the function. That has a set of consequences:

  • we can focus on writing tests that check only the function,
  • our test does not care much about the implementation of the and functions,
  • if either or function breaks, the unit test we wrote for will still be successful.
parsePeople.test.ts

The issue of sharing mocks between tests

Unfortunately, some of the above tests are failing.

Let’s look at the reason why those tests are not passing.

Error:

Expected number of calls: 0
Received number of calls: 4

When we look at the implementation of the function, we can see that if there are no arguments, the function returns immediately. This is because Jest shares our mocks between tests in a particular test file by default.

parsePeople.test.ts

Above, in the first test, we call the with an array. Because of that, it calls the mock function under the hood.
When we execute the second test right after the first one, the mock is already called. Because of that, fails.

Clearing mocks

To deal with the above issue, we can force Jest to clear all the mocks after each test.

parsePeople.test.ts

When we run , Jest clears the information stored in the , , and arrays of all mocks. Thanks to doing that after each test, the counter of calls made to our mock function contains zero. Because of that, our tests no longer fail.

If we want to clear just a particular mock, we can call

If we want to clear the mocks after every test in all of our test files, we can add to our Jest configuration instead of running .

Resetting mocks

Besides the above, we also have the function that is quite similar. Besides clearing the , , and properties, it also removes the implementation of our mock.

If we want to reset only one mock, we can use the function.

We might want to reset the mock after each of our tests in all of our test files. In this case, we need to set in our Jest configuration.

An important caveat is that Create React App sets to by default.

Summary

In this article, we’ve created mock functions and defined their implementation. We’ve also learned different ways of spying on a mock function. Finally, we wrote a unit test that uses all of the above knowledge to internalize this knowledge better. While doing that, we stumbled upon an issue of sharing mocks. Besides that, we learned how to clear the mocks between tests to prevent our tests from failing.

Series Navigation

<< JavaScript testing #10. Advanced mocking with Jest and React Testing Library



Source link

Leave a reply

Please enter your comment!
Please enter your name here