JS: Advanced Testing
Theory: Mocking
There's a concept called “mocking” that's very popular in testing. It's technically quite similar to stubbing, so the two are often confused (either intentionally or unintentionally). However, they serve different purposes and are used in different situations. Let's figure out what it is and when you need it.
Up until this point, we've viewed side effects as a hindrance to testing our logic. Stubs were used to isolate them, or the logic was specifically switched off in the test environment. After that, we were able to safely check the function worked properly.
Some situations require something else. Not the result of the function itself, but whether it performs the action we want, for example, does it send the right HTTP request with the right parameters? To do this, you'll need mocks. Mocks check how the code is executed.
HTTP
This is called mocking. Mocks check that a given piece of code has been executed in a certain way. This can be a function call, an HTTP request, and so on. The job of the mock is to make sure that this happened, and find out how exactly it happened, for example, it can check that specific data was passed to the function.
What does this test do for us? In this case, not much. Yes, we're sure there was a call, but that in itself does not tell us anything. So when are mocks useful?
Imagine if we were developing the @octokit/rest library, the same library that makes requests to the GitHub API. The whole point of this library is to run the right queries with the right parameters.Therefore, it is necessary to check the execution of requests with the exact URLs. Only then can we be sure that it carries out the right requests.
This is the key difference between mocks and stubs. Stubs eliminate side effects, so as not to interfere with the verification of the result of the code, such as the return of data from a function. Mocks focus on exactly how the code works, what it does internally. In this purely technical way moc and stub are created the same way, except that the moc hangs waiting, checking calls. This leads to confusion because stubs are often referred to as mocks. There's nothing you can do about it, but try to understand for yourself what's being talked about. This is important, as the focus of the tests depends on it.
Functions
Mocks are quite often used with functions and methods. For example, they can check:
- That the function was called, and how many times it was called
- What arguments were passed to the function and how many
- What the function returned
Suppose we want to test the forEach function. It calls a callback for each item in the collection:
This function doesn't return anything, so you can't test it directly. You can try to do it with mocks. Let's check that it calls the passed callback and passes the desired values to it.
Since we're looking at Jest, we'll use Jest's built in mechanism to create mocks. Other frameworks may have their own built-in mechanisms. In addition, as we've seen above, there are specialized libraries for mocks and stubs.
Using mocks, we were able to check that the function was called exactly three times, and a new element of the collection was consecutively passed to it for each call. In theory, we can say that this test really tests if forEach() works. But can it be done in a simpler way, without using mocks and without binding it to internal behavior? It turns out you can. To do this, we just need to use closure:
Objects
Jest allows you to create mocks for objects as well. They're done with jest.fn(), too, because it returns a constructor:
Through this moc you can find out any information about all the copies:
Advantages and disadvantages
Although there are situations in which mocks are necessary, in most situations they should be avoided. Mocks know too much about how the code works. Any test with mocks goes from a black box test to a white box one. The widespread use of mocks leads to two things:
- After refactoring, you have to rewrite the tests (and there's a lot of them!), even if the code works correctly. This happens because of how they're bound to how the code works.
- The code may stop working, but the tests pass because they focus not on its results, but on how it works internally.
Use real code wherever possible. If you can be sure that your code works without mocks, don't use mocks. Excessive mocking makes the tests useless and tricky to maintain. Black box tests are ideal.

