I am learning NodeJs and Jest. I am having trouble with unit tests. I just translated my actual code to a simple logic. I have two files as below.
// age.js
function getAge(birthYear) {
const age = 2021-birthYear;
return age
}
module.exports = { getAge }
// user.js
const { getAge } = require("./age");
async function isMinor(){
const bYear = 1991
const age = await getAge(bYear)
if( age <= 18) {
return true
}
return false
}
module.exports = { isMinor }
isMinor calls getAge from another file, I want to test isMinor without actually calling getAge. I referred to this article and wrote my test, but I still encountered some issues.
// user.test.js
const { isMinor } = require("./user")
describe("Age Test", () => {
// Question 1: how can I properly import getAge function here and mock a return value for it? I also tried mockImplementation and mockReturnedValue, but they didn't work
// I don't want to actually invoke getAge function
beforeEach(() => {
jest.mock("./age", () => ({
getAge: () => 99,
}))
})
// Question 2: How can I teardown the moch after the test
afterEach(() =>{
getAge.mockRestore()
})
test("should be an adult", async () => {
const isMinor = await isMinor();
expect(isMinor).toEqual(false);
});
});
I expect to receive 99 from getAge, but it returns null. I appreciate any helps. Thank you.
Since you're only testing isMinor with mock values you'll want to test it with multiple values to cover all of the different scenarios (branches), so you can create a mock for the ./age.js only once by simply calling:
const { getAge } = require('./age');
jest.mock('./age');
It will generate a mock function for each module function only for this test file
Modules that are mocked with jest.mock are mocked only for the file that calls jest.mock. Another file that imports the module will get the original implementation even if it runs after the test file that mocks the module.
So there will be no need for you to restore the original implementation.
The biggest advantage from using auto-mocks is when the method from the implementation (in this case getAge) is removed - the test will fail.
The only thing left to do would be to set the mock's return value that you want to test with. And since it's expected to return a promise you should use .mockResolvedValue()
user.test.js
const { isMinor } = require("./user");
const { getAge } = require('./age');
jest.mock('./age');
describe("Age Test", () => {
describe('getAge returning more than 18', () => {
beforeAll(() => {
getAge.mockResolvedValue(99)
})
test("should be an adult", async () => {
expect(await isMinor()).toEqual(false);
});
})
describe('getAge returning less than 18', () => {
beforeAll(() => {
getAge.mockResolvedValue(13)
})
test("should be a minor", async () => {
expect(await isMinor()).toEqual(true);
});
})
});
Working example
Below example use "jest": "^26.6.3".
user.js:
const { getAge } = require('./age');
async function isMinor() {
const bYear = 1991;
const age = await getAge(bYear);
console.log('age: ', age);
if (age <= 18) {
return true;
}
return false;
}
module.exports = { isMinor };
Option 1: use jest.mock() in beforeEach hook functional scope, it will NOT be hoised to the top of the code. So you need to require modules after mocking by jest.mock() method.
describe('Age Test', () => {
beforeEach(() => {
jest.mock('./age', () => ({
getAge: jest.fn(() => 99),
}));
});
test('should be an adult', async () => {
const { isMinor } = require('./user');
const { getAge } = require('./age');
const actual = await isMinor();
expect(actual).toBeFalsy();
expect(getAge).toBeCalledWith(1991);
});
});
unit test result:
PASS examples/66288290/user.test.js
Age Test
✓ should be an adult (1911 ms)
console.log
age: 99
at examples/66288290/user.js:6:11
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 87.5 | 50 | 100 | 87.5 |
user.js | 87.5 | 50 | 100 | 87.5 | 8
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 3.197 s
Option 2: use jest.mock() in the module scope, it will be hoisted to the top of the code. Even if you require the modules at the top of the file. The ./age module you require is already be mocked.
const { isMinor } = require('./user');
const { getAge } = require('./age');
jest.mock('./age', () => ({
getAge: jest.fn(() => 99),
}));
describe('Age Test', () => {
afterAll(() => {
jest.resetAllMocks();
});
test('should be an adult', async () => {
const actual = await isMinor();
expect(actual).toBeFalsy();
expect(getAge).toBeCalledWith(1991);
});
});
unit test result:
PASS examples/66288290/user.test.js
Age Test
✓ should be an adult (11 ms)
console.log
age: 99
at examples/66288290/user.js:6:11
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 87.5 | 50 | 100 | 87.5 |
user.js | 87.5 | 50 | 100 | 87.5 | 8
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 3.502 s
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With