Mastering Unit Testing: How to Mock Dependencies with Jest
source link: https://blog.bitsrc.io/mock-dependencies-using-jest-274c31ec9cd0
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Mastering Unit Testing: How to Mock Dependencies with Jest
I started to practice test-driven programming a couple of years back. Depending on each project's settings, the strictness of the unit test can vary. For instance, one project requires to have a coverage coupled so if the percentage isn’t met, the code can’t be committed. Therefore I have to refresh all the possible ways to mock things under Jest.
Baseline of the mocking
Let’s start with easy ones with a function that is relatively easy to test:
function app = (fn) => {
const a = fn()
...
return ...
}
In order to test app
we can make a mock replacement for fn
and then pass it into your app
as usual:
test('app', () => {
let mockedFn = jest.fn()
app(mockedFn)
expect(mockedFn).toHaveBeenCalled()
})
Not only the above approach allows our function to be tested, but also it provides us an extreme level of confidence. As long as we can provide a fn
we can control the robustness of this unit, even when it’s a black box with no access to the implementation. Notice the reason for our confidence level isn’t based on luck, instead, it’s that the dependencies are all declared on the interface. Exactly for this reason, when designing your own function, you should aim for this goal because it’ll make your life a lot easier.
When testing gets difficult
Unfortunately, we can’t always ask for the perfect interface design. Half of the time, unless writing a library from scratch, you will run into a case where the written function doesn’t carry that implicit interface. Let’s take a look at one example:
import axios from 'axios'
function app() {
const user = axios.get('qplot.com');
if (!user) return null
// now we can use this user
return ...
}
We want to test a function where inside we fetch the user over the internet and then we move to the rest of the code based on this user object. Naive or small as the function looks, it’s kind of very difficult to write a test. Believe it or not, this is the number one place where a junior developer quits the test job that he is supposed to perform. Let’stake a second to understand why it’s that.
The function app
doesn’t have input parameters, aka explicit interface, however, it has a hidden dependency axios
which isn’t written but referenced inside. Reflecting on what we have stated at the beginning unless we can control all dependencies precisely, we won’t gain the confidence that we need. Therefore, the solution is to find a way to mock axios
so that we can control how it behaves in the testing environment. Let me show you one quick example of mocking it:
import axois from 'axois'
jest.mock('axios', () => ({
get: (url) => Promise.resolve('fang')
}))
test('app', () => {
app()
// do rest of testing
})
We first import the axios
and acknowledge there’s a get
function that we want to mock the response. Therefore whenever inside the app
when axios.get
is called, it’ll run our mocked version, in this particular test we want to set user
equals to fang
without going a round trip to the internet.
Ok, let’s retrospect a bit, in order to test a unit, we should find out all apparent and hidden dependencies, and make sure you can mock all of them to recover the perfect crime scene. After that based on these capabilities, then you can come up with any number of tests to back your functionalities.
People might argue here, mocking
axios
seems like dodging the testing responsibility. This is where the unit testing and rest of the testings start to branch out. Here we don’t really care aboutaxios
, in a way, even whenaxios
is broken, we’d like to finish our test. This is actually the definition of a unit test in a verbose form.
You might wonder why we ever want to have hidden dependencies. This is a more philosophical question: if you live in a world, you can’t live by yourself, no matter how hard you try, you will end up with some relationship that is not apparent to the surface. Writing a function with all sorts of dependencies operates in a similar fashion. Technically, you can call the source of these dependencies from global variables, external libraries, or side effects. However you understand them, they are in general not under our control.
💡 Dependency-management becomes infinitely more streamlined if you use Bit. Within a Bit Workspace, you don’t need to tell npm, pnpm, or yarn which component dependencies need to be installed, nor if it’s a dev or prod dependency. Bit dynamically generates package.json files for each, and can handle this for you painlessly. Find out more about this hereand here.
Learn more here:
Advanced Bit Dependency Management and Configs
Bit makes it simple to define and manage dependencies at any scale. Among other things, this means that `bit install`…
Summary
I showed you ways to mock both apparent and hidden dependencies. So when you write tests, find them first, and then mock them, happy coding ;)
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK