What mocks are? If you are curious you need to read this article. We'll check different kinds of mocks and we'll use them in practice.
During the earlier lesson, we learned the stubs and realized what they are used for. Now is the time for mocks, which are used very often during unit testing.
Mocks are used to replace the implementation of a specific module. Their main objective is to provide:
- Isolation - you can define the expected behavior of the mock and specify the responses
- Control - they can simulate different scenarios and edge cases that may be challenging to reproduce with real dependencies
- Simplicity - they eliminate the need for complex setups or configurations that may be required for using real dependencies
- Performance - mocks can be faster and more efficient than using real dependencies
- Testing unavailable or unreliable modules - by mocking module which is broken you can test the behavior of your code
Why are mocks so important?
Think of a situation where you make a query to a mailing service in the code that you are testing. If you don't mock the API of that service then you're making real queries.
It would cost you a bit 💰.
Why do we need to use mocks?
Let's say we have the following function that is designed to build a path for specific article:
What catches the eye immediately is the use of the path module and the globally available process variable.
The path module is not a part of our application, but an external library - we assume that it works properly, we don't want to test it.
The process variable is an object created by the NodeJS runtime environment. It has properties and methods. Also, we have to assume that it works correctly and we don't want to test it.
How should we test it? We need to mock both and simulate the behavior of the cmd() and join() methods. We know how they work so it won't be a problem.
This is how the join() and cwd() methods work:
Now all we need to do is create a test in which we substitute their implementation and check whether our own function works as expected.
In the first mock we replace the implementation of the path module and the join method. Thanks to the jest.fn() we will be able to change the implementation in any other test as needed.
In the second one, we used defineProperty to override a globally available process object property.
It is very important to assign a starting value to global objects after testing. If we don't do this, we expose ourselves to side effects and the fact that tests in this file can affect other tests - which is very bad!
Note the use of the object descriptor (writeable: true), which allowed us to restore to the initial value later. Without this change, it is not possible to overwrite the cwd property implementation.
Bravo! Now you know how important mocks are and how to create them for modules, methods and global objects. You have to admit that their use is much wider than in the case of stubs.
If you enjoyed it, be sure to visit us on Linkedin where we regularly upload content from programming.
- 1. Basics
Grouping the tests
The usage of describe and it
The best practices for naming tests
Navigating the different types of software tests
- 2. Mastering unit testing
Project and tests setup
Unit tests review
React component testing
Snapshot testing in React
Understanding stubs in testing
Understanding mocks in testing
Creating testing fixtures
Using spies in React and Typescript
Mocking environment variables
Using dependency injection pattern to improve fixtures
- 3. Mastering integration testing
Understanding the integration tests
Using MSW library to remove implementation details from tests
Creating fixture for MSW to reduce boilerplate and setup
- 4. Mastering e2e tests