I'm trying to write a unit test for a Node.js project's logic using Jest.
However, most documentations only provide a case for importing a module or class, however, in my case, my module only contains functions.
So far I know that there are mainly three ways to test a function in Jest:
1) jest.fn()
2) jest.spyOn
3) jest.mock('path')
I have tried all three but none work.
I wanted to test if the function returns a correct value(string) when called.
I tried many different
Here's my code: (I will show short snippets of my code in the later parts)
getDefApCode.ts
export function getDefApCode(code: string) {
switch (code) {
case 'BKK':
return 'NRT'
case 'CTX':
return 'ICN'
case 'SIN':
return 'TPE'
default:
return code
}
}
export function getDefaultDepartureCode(code: string) {
return code ? getDefaultLocationCode(code) : 'LHR'
}
export function getDefaultDestinationCode(code: string) {
return code ? getDefaultLocationCode(code) : 'ZRH'
}
getDefAPCode.spec.ts >> Pattern 1 (using required + jest.fn)
import { Connection, getConnection, getConnectionOptions } from "typeorm";
import { bootstrap, dbConnection } from "../../../src/app";
import { TourSearchParamsFactory } from "../../helpers/typeOrmFactory";
import * as getDefAPCode from "../../../src/controllers/logic/getDefAPCode";
describe("Logic Test", () => {
beforeAll(async () => {
await dbConnection(15, 3000);
});
afterAll(async () => {
const conn = getConnection();
await conn.close();
});
it("should get a default location code", async () => {
const getLocation = require('../../../src/controllers/logic/getDefAPCode');
const code = jest.fn(code => 'BKK');
const getCode = getLocation(code);
expect(getCode).toHaveBeenCalled();
});
});
Error Message:
TypeError: getLocation is not a function
getDefAPCode.spec.ts >> Pattern 2 (using spyON)
import { Connection, getConnection, getConnectionOptions } from "typeorm";
import { bootstrap, dbConnection } from "../../../src/app";
import { TourSearchParamsFactory } from "../../helpers/typeOrmFactory";
import * as getDefaultLocationCode from "../../../src/controllers/logic/getDefaultLocationCode";
describe("Logic Test", () => {
beforeAll(async () => {
await dbConnection(15, 3000);
});
afterAll(async () => {
const conn = getConnection();
await conn.close();
});
const { getDefaultLocationCode, getDefaultDepartureCode, getDefaultDestinationCode } = require('../../../src/controllers/logic/getDefaultLocationCode');
it("should get a default location code", async () => {
const spy = jest.spyOn(getDefaultLocationCode, 'getDefaultLocationCode');
getDefaultLocationCode.getDefaultLocationCode('AKJ');
expect(spy).toHaveBeenCalled();
});
});
These are some error messages appear when I tried a different pattern (I didn't keep track of all of the test code pattern, will add the test code pattern once I fixed docker)
Error Message:
Cannot spy the getDefaultLocationCode property because it is not a function; undefined given instead
31 | const spy = jest.spyOn(getDefaultLocationCode, 'getDefaultLocationCode');
Past Error Messages
error TS2349: This expression is not callable.
Type 'typeof import("/app/src/controllers/logic/getDefAPCode")' has no call signatures.
another one
expect(received).toHaveBeenCalled()
Matcher error: received value must be a mock or spy function
Received has type: string
Received has value: "NRT"
I figured out that I don't have to use mock function in this case.
I stored argument in a variable and then I use the variable instead using a string directly.
Here's how I edit my test code
it("should get a default location code", () => {
const code = 'BKK';
expect(code).toHaveBeenCalled();
});
Related
I am trying to mock a method used by the class I'm testing. The method is located in the same file as the class I'm testing.
The tested file:
export function trackErrors() {
console.log("This SHOULD NOT BE SEEN ON tests");
// Currently this console log is visible hwne running tests
}
// Component that is tested
export const form = () => {
return <FormComponent callback={trackErrors} />
}
The test itself:
With mock
The jest.mock() is the first thing to happen, before the import.
jest.mock('./testedFile', () => {
const actualModule = jest.requireActual('./testedFile')
return new Proxy(actualModule, {
get: (target, property) => {
switch (property) {
case 'trackErrors': {
return jest.fn()
}
default: {
return target[property]
}
}
},
})
})
import * as SModule from "./testedFile";
it.only("Calls trackErrors", async () => {
const { getByTestId } = <SModule.form />;
await typeEmail(getByTestId, "abcd");
await waitFor(() => {
expect(SModule.trackErrors).toHaveBeenCalledTimes(1);
});
});
This does not work. When printing SModule.trackErrors, I can see the mocked function being put out. But when the test is executed, the test fails with 0 calls and I see this console.log This SHOULD NOT BE SEEN ON tests. That means that the method is mocked good inside the test, but when rendering the component it continues to call the prexistent method (the one not modked).
I've also tried the spyOn approach, but the outcome is exactly the same.
I'm attempting to mock a call to a class instance function on an ES6 module being imported by the code under test. I've followed the progress of the ES6 support and eventually stumbled onto this PR https://github.com/facebook/jest/pull/10976 which mentioned that in 27.1.1 support for jest.unstable_mockModule was added. I upgraded my version of Jest to take advantage and while the test doesn't error, it also doesn't seem to actually mock the module either.
This is the module under test:
// src/Main.mjs
import Responder from './Responder.mjs'
import Notifier from './Notifier.mjs'
export default {
async fetch(request, environment, context) {
let response
try {
response = new Responder(request, environment, context).respond()
} catch (error) {
return new Notifier().notify(error)
}
return response
}
}
Here is the test:
// test/Main.test.mjs
import { jest } from '#jest/globals'
import main from '../src/Main.mjs'
describe('fetch', () => {
test('Notifies on error', async () => {
const mockNotify = jest.fn();
jest.unstable_mockModule('../src/Notifier.mjs', () => ({
notify: mockNotify
}))
const notifierMock = await import('../src/Notifier.mjs');
await main.fetch(null, null, null)
expect(mockNotify).toHaveBeenCalled()
})
})
I'm trying to mock the call to Notify to expect it to have been called and while this will run, it raises an exception from inside the Notifier.notify() that is supposed to be mocked, so it appears that it isn't being mocked at all.
What am I missing? Any help is much appreciated. 🙏
I believe it's because you're importing main at the start of the file. You need to do a dynamic import the same way as Notifier.mjs
// test/Main.test.mjs
import { jest } from '#jest/globals'
describe('fetch', () => {
test('Notifies on error', async () => {
const mockNotify = jest.fn();
jest.unstable_mockModule('../src/Notifier.mjs', () => ({
notify: mockNotify
}))
const notifierMock = await import('../src/Notifier.mjs');
const main = await import('../src/Main.mjs');
await main.fetch(null, null, null)
expect(mockNotify).toHaveBeenCalled()
})
})
I am trying to write a unit test for a react component. It's a fairly standard component which calls a promise-returning method and uses 'then' and 'catch' to handle the resolution. My test is trying to validate that it calls the correct method when the promise is rejected however despite following what i believe is a standard patttern, I cannot get jest to validate the call. I have listed the relevant files here and have also put up a github sample, which is linked at the bottom of the question. That sample is simply a new react app created using npx and the files below added in.
Here is my example component:
import React from 'react';
import api from '../api/ListApi';
class ListComponent extends React.Component {
constructor(props) {
super(props);
this.fetchListSuccess = this.fetchListSuccess.bind(this);
this.fetchListFailed = this.fetchListFailed.bind(this);
}
fetchList() {
api.getList()
.then(this.fetchListSuccess)
.catch(this.fetchListFailed);
}
fetchListSuccess(response) {
console.log({response});
};
fetchListFailed(error) {
console.log({error});
};
render() {
return(<div>Some content</div>);
};
}
export default ListComponent;
Here is the api class (note, the api doesnt exist if you run the app, its just here for example):
const getList = () => fetch("http://someApiWhichDoesNotExist/GetList");
export default { getList };
And here is the test case:
import ListComponent from './ListComponent';
import api from '../api//ListApi';
describe('ListComponent > fetchList() > When the call to getList fails', () => {
it('Should call fetchListFailed with the error', async () => {
expect.hasAssertions();
//Arrange
const error = { message: "some error" };
const errorResponse = () => Promise.reject(error);
const componentInstance = new ListComponent();
api.getList = jest.fn(() => errorResponse());
componentInstance.fetchListFailed = jest.fn(() => { });
//Act
componentInstance.fetchList();
//Assert
try {
await errorResponse;
} catch (er) {
expect(componentInstance.fetchListFailed).toHaveBeenCalledWith(error);
}
});
});
The problem is that the test is not executing the catch block, so, in this case, the expect.hasAssertions() is failing the test. Can anyone help me understand the catch block is not executing? Wrapping the await in the try block and asserting in the catch seems to be a standard pattern in the docs but I am fairly new to Js and React and am obviously doing something wrong.
Here is the sample project on GitHub. Any help would be greatly appreciated =)
In your console:
const errorResponse = () => Promise.reject();
await errorResponse;
//() => Promise.reject()
You're awaiting a function, not the result of the call to that function. You want to:
await errorResponse();
EDIT:
In addition to that, the rest of your test is confusing. I believe you actually want to test what happens when the fetchList method of your component is called, and it fails, I assume. So you need to call it in your test, and await it's response:
Update your component's fetchList method to return the promise.
await componentInstance.fetchList() instead of await errorResponse()
Because you catch the error in fetchList you'll never enter the catch or the try...catch so your final test should look like this:
Test:
//Arrange
const error = { message: "some error" };
const errorResponse = () => Promise.reject(error);
const componentInstance = new ListComponent();
api.getList = jest.fn(() => errorResponse());
componentInstance.fetchListFailed = jest.fn(() => { });
//Act
await componentInstance.fetchList();
expect(componentInstance.fetchListFailed).toHaveBeenCalledWith(error);
Component:
fetchList() {
return api.getList()
.then(this.fetchListSuccess)
.catch(this.fetchListFailed);
}
My two cents, with my own case in a React native app:
In my component I have:
const handleRedirect = React.useCallback(() => {
client.clearStore()
.then(navigation.push('SomeScreen'))
.catch(e => logger.error('error', e));
}, []);
My test is this one, where I want to test if the promise is rejected:
it('should throw an exception on clearStore rejected', async () => {
const client = {
clearStore: jest.fn()
};
const error = new Error('clearStore failed');
client.clearStore.mockReturnValue(Promise.reject(error));
expect.assertions(1);
await expect(client.clearStore())
.rejects.toEqual(Error('clearStore failed'));
});
Given the test codes below:
subAuthCall:
const sinon = require('sinon')
const sandbox = sinon.createSandbox()
function stubSuccessCall() {
return sandbox.stub(authorization, 'authorize').returns({enabled: true});
}
function stubFailedCall() {
return sandbox.stub(authorization, 'authorize').returns({enabled: false});
function restoreSub() {
sandbox.restore();
}
in the authorization.js file, I have:
function authorize(data) {
return data.enabled;
}
module.exports = {
authorize
}
and then in the middleware, I have:
const {authorize} = require('./authorization')
async function check() {
//import auth data
console.log(authorize(data))
if (authorize(data)) {
//resolve to true
} else {
//reject
}
}
Then in the test case, I called:
afterEach(authorizationStub.restorStub);
describe('auth testing', () => {
it('test successful', () => {
authorizationStub.stubSuccessCall();
return check().then(res => {expect(res.result).to.equl(true)});
})
it('test failed', () => {
authorizationStub.stubFailedCall();
return check().then(res => {expect(res.result).to.equl(false)});
})
})
It's a overly simplified auth logic and test case. The weird problem I had is that if I run both test cases, it prints out:
1 - test successful
true // from console.log(authorize(data))
2 - test failed
true // from console.log(authorize(data))
The 1st test case passed and the 2nd one failed (because the stubbing didn't return the right result)
but in the test failed, case, it supposed to return false as how I stub it in stubFailedCall, but it still has the same result in stubSuccessCall. I have verified the restoreStub is called.
I accidentally came across the fix: In the middleware, I used to have:
const {authorize} = require('./authorization')
...
but if I changed this to:
const auth = require('./authorization')
and then in the codes, instead of:
authorize(data)
I do:
auth.authorize(data)
This will work - both test cases will pass without any other changes.
My question is, why sinon stub/restoring stub didn't work if I deconstruct the authorize call in the middleware, but if I use an object to make the call, it will work? What's the mechanism behind this?
Trying to mock one of the function with callback from api and getting error as TypeError: specificMockImpl.apply is not a function
import { IEnvironmentMap, load } from 'dotenv-extended';
import { getTokensWithAuthCode, sdk } from '../src/connection-manager';
describe('getTokensWithAuthCode function Tests', () => {
jest.useFakeTimers();
let boxConfig: IEnvironmentMap;
beforeAll(() => {
boxConfig = load({
errorOnMissing: true,
});
});
it('should reject a promise if there is wrong auth code provided', async () => {
sdk.getTokensAuthorizationCodeGrant = jest.fn().mockImplementation(boxConfig.BOX_AUTH_CODE, null, cb => {
cb('Error', null);
});
try {
const tokens = await getTokensWithAuthCode();
} catch (error) {
expect(error).toBe('Error');
}
});
});
And my function which is trying to test is as follow:
import * as BoxSDK from 'box-node-sdk';
import { IEnvironmentMap, load } from 'dotenv-extended';
import {ITokenInfo} from '../typings/box-node-sdk';
const boxConfig: IEnvironmentMap = load({
errorOnMissing: true,
});
export const sdk: BoxSDK = new BoxSDK({
clientID: boxConfig.BOX_CLIENT_ID,
clientSecret: boxConfig.BOX_CLIENT_SECRET,
});
/**
* - Use the provided AUTH_CODE to get the tokens (access + refresh)
* - Handle saving to local file if no external storage is provided.
*/
export async function getTokensWithAuthCode() {
return new Promise((resolve: (tokenInfo: ITokenInfo) => void, reject: (err: Error) => void) => {
if (boxConfig.BOX_AUTH_CODE === '') {
reject(new Error('No Auth Code provided. Please provide auth code as env variable.'));
}
sdk.getTokensAuthorizationCodeGrant(boxConfig.BOX_AUTH_CODE, null, (err: Error, tokenInfo: ITokenInfo) => {
if (err !== null) {
reject(err);
}
resolve(tokenInfo);
});
});
}
Is there any other way to mock function in jest? I have read an article https://www.zhubert.com/blog/2017/04/12/testing-with-jest/
On this line, rather than pass a function to mockImplementation, you're passing three arguments:
jest.fn().mockImplementation(boxConfig.BOX_AUTH_CODE, null, cb => {
cb('Error', null);
});
It looks like you might have just missed some braces. Try switching it to:
jest.fn().mockImplementation((boxConfig.BOX_AUTH_CODE, null, cb) => {
cb('Error', null);
});
It's better not trying to mutate a const used elsewhere.
You could change getTokensWithAuthCode to make it receive sdk as parameter, thus in your test you would pass the mock function as argument, therefore having a more predictable behavior than mutating directly sdk.
In your code, you could make a second getTokensWithAuthCode implementation, with the signature getTokensWithAuthCodeUnbound(sdk) for example, and export it. This implementation will be used in your tests.
Exporting using the same getTokensWithAuthCode name, you would call:
export const getTokensWithAuthCode = getTokensWithAuthCodeUnbound.bind(null, sdk)
That way, your app will use getTokensWithAuthCodeUnbound bound with the default sdk, and you can test more easily its implementation.
Mozilla Developer Network (MDN) bind documentation.