Intro
Optimize Jest fixtures using dependency injection. Inject environment functions to streamline code, ease test creation.
The problem to solve
In previous lesson - ⭐ Mocking environment variables, we created a nice working fixture that reduces lines of code when we write tests.
However, the result is not "ideal". Still, we may reduce and remove some responsibilities from developers. Check the following code to understand what I mean:
Loading
So, imagine a situation when you forget to add afterEach and fixture.restore() statement in one of your tests... It may happen because the fixture.restore() call cleans up all mocks in the environment object. Yea, it will be pretty hard to spot what is causing false positives or false negatives in our tests.
Good news! Dependency injection pattern will fix all our problems 💨.
The dependency injection pattern
Let's explain it with another example not connected with our problem to understand the basic idea. Imagine that you've installed a library called "users-list" which is a React component with sorting capabilities (yea, really complicated :P).
The implementation of component looks like this (remember it's a library code - in your app you just installed it):
Loading
Now, imagine that the implementation of this component sorting mechanism is broken - looks like the sort function from Lodash has a bug!
Loading
So, what's now? What kind of options do you have? You can try to find another lib, copy the lib source code to your code, wait for a fix, or implement it completely on your own... All of these options are time-consuming and it's just a sort function bug...
If the author of the library would add one small change, we would not have to do anything... Check the code below to understand how we may improve it:
Loading
Instead of relying on and being coupled with Lodash without the option to override sort mechanism, we've added UsersListProps interface, to be able to change sorting mechanism. That's how dependency injection pattern works in its simplest form.
You need to pass a function that implements the required contract - in our case specified via TypeScript interface. Now, we still are coupled with Lodash, but we may replace it from consumer code, not inside UsersList library implementation (in addition we improved our code from the SOLID perspective - the Open/Close principle.
The last change must be implemented in the consumer component and our little bug will be fixed without any additional effort.
Loading
Dependency injection pattern may be implemented in many different ways. We may use injection tokens and more advanced mechanisms. The presented approach is the simplest implementation of it!
Fixing our fixture with dependency injection
We'll use the same technique as above. We'll pass afterEach function from Jest to our fixture. When the fixture is called the first time, it will trigger afterEach, so in test files all of these statements will be removed.
Loading
First, we added an additional type to define the signature of afterEach function. We used higher order function pattern and closure mechanism to be able to call our fixture function twice.
Second, with injected afterEach and passed the key that is required. Thanks to the closure, afterEach callback will be remembered and we'll be able to do stuff like this:
Loading
Small note
If you would like to add additional logic to afterEach and you're using this fixture - don't worry about anything, the Jest will run as many callbacks as you define :).
Loading
So, you will have always called all defined afterEach functions, the one that we're passing and the others defined in tests. Thanks to that you're not limited to only one afterEach call.
The final result
Loading
Conclusions and thoughts
As you saw we've added a nice improvement for developers that reduces the code that they need to write and the responsibility to "remember". In this particular fixture, it was just 3 lines of code and one responsibility, but in more advanced ones it may be much more!
To see more advanced fixtures you may read ⭐ Creating fixture for MSW to reduce boilerplate and setup.
- 1. Basics
10 minutes
Software testing
2 m
Grouping the tests
1 minute
The usage of describe and it
2 m
The best practices for naming tests
2 m
Navigating the different types of software tests
3 m
- 2. Mastering unit testing
38 minutes
Project and tests setup
3 m
Unit tests review
4 m
React component testing
5 m
Snapshot testing in React
4 m
Understanding stubs in testing
3 m
Understanding mocks in testing
5 m
Creating testing fixtures
4 m
Using spies in React and Typescript
3 m
Mocking environment variables
3 m
Using dependency injection pattern to improve fixtures
4 m
- 3. Mastering integration testing
12 minutes
Understanding the integration tests
4 m
Using MSW library to remove implementation details from tests
4 m
Creating fixture for MSW to reduce boilerplate and setup
4 m
- 4. Mastering e2e tests
8 minutes
Comments
Add your honest opinion about this article and help us improve the content.