So, this is actually something that happened to my several times and I never knew how to solve it.
I have a class like this
//myClass.js
const { get } = require('../models/myModel');
class MyClass {
constructor(id) {
this.id = id;
}
async getByID() {
return get(this.id);
}
}
and a controller that is
//controller.js
module.exports = MyClass;
const MyClass = require('./MyClass.js');
module.exports = {
getController: (req, res, next) => {
try {
const MyObject = new MyClass(1);
const info = await MyObject.getByID();
res.send(info)
}
catch (e) {
next(e);
}
}
}
When I want to do an E2E test, i need to mock that getByID to see what happen when it resolves and rejects.
So, how do I mock a constructor? I did something like this
// myclass.spec.js
const MyClass = require('./MyClass.js');
jest.mock('./MyClass', () => jest.fn().mockImplementation(() => ({
getByID: Promise.resolve({id: 1, name: 'John'}),
})));
describe('Testing MyClass', () => {
it('Should return info', () => {
const res = httpMocks.createResponse();
const mReq = httpMocks.createRequest();
const mNext = jest.fn();
const mRes = mockResponse();
await pipelineController(mReq, mRes, mNext);
expect(mRes.send).toHaveBeenCalledWith(1);
done();
})
})
I know this test currently works, but now I cant change that getByID mock value to see what happen when it rejects the promise.
If I try to include it inside the test (it) .... it won't mock anything...
I want something like
const MyClass = require('./MyClass.js');
const {pipelineController} = require('./controllers.js')
jest.mock('./MyClass', () => jest.fn().mockImplementation(() => ({
getInfo: jest.fn(),
})));
describe('Testing MyClass', () => {
it('Should return info', () => {
ProcessService.getInfo.mockImplementation(() => Promise.resolve('the value i want'))
const res = httpMocks.createResponse();
const mReq = httpMocks.createRequest();
const mNext = jest.fn();
const mRes = mockResponse();
await pipelineController(mReq, mRes, mNext);
expect(mRes.send).toHaveBeenCalledWith(1);
done();
})
it('Should return info', () => {
ProcessService.getInfo.mockImplementation(() => Promise.reject('the reason i want'))
const res = httpMocks.createResponse();
const mReq = httpMocks.createRequest();
const mNext = jest.fn();
const mRes = mockResponse();
await pipelineController(mReq, mRes, mNext);
expect(mRes.send).toHaveBeenCalledWith(1);
done();
})
})
Since getByID is called right after instantiation, a spy should be available outside mocked class in order to change the implementation.
This can be done with class auto-mock that works in obscure and unspecified ways but may be suitable for this specific case:
jest.mock('./MyClass.js')
const MyClass = require('./MyClass.js');
...
MyClass.prototype.getByID.mockResolvedValue({id: 1, name: 'John'});
// instantiate MyClass and call getByID
And this can be done by exposing a spy outside the class:
const mockGetByID = jest.fn();
jest.mock('../../../services/ProcessService', () => jest.fn(() => (
{ getByID: mockGetByID }
)));
const MyClass = require('./MyClass.js');
...
mockGetByID.mockResolvedValue({id: 1, name: 'John'});
// instantiate MyClass and call getByID
Related
I did the tests in the following way, but it bothers me how I need to import an object with the functions, instead of just importing the functions.
this works
//service.test.js
const get = require('../../modules/bankUser/model/getRegisteredUser');
get.getRegisteredUser = jest.fn()
.mockImplementationOnce(async () => mock)
.mockImplementationOnce(async () => mockUpdated);
//service.js
const get = require('../model/getRegisteredUser');
const testedFunction () => {
const depositReciver = await get.getRegisteredUser(depositName, depositCpf);
}
this dosent
//service.test.js
const get = require('../../modules/bankUser/model/getRegisteredUser');
get.getRegisteredUser = jest.fn()
.mockImplementationOnce(async () => mock)
.mockImplementationOnce(async () => mockUpdated);
//service.js
const { getRegisteredUser } = require('../model/getRegisteredUser');
const testedFunction () => {
const depositReciver = await getRegisteredUser(depositName, depositCpf);
}
I'm triyng this (way):
//service.test.js
const get = require('../../modules/bankUser/model/getRegisteredUser');
jest.mock('../../modules/bankUser/model/getRegisteredUser');
get.mockImplementationOnce(() => ({ getRegisteredUser: () => mockObject }));
jest returns this:
TypeError: get.mockImplementationOnce is not a function
also I've tried to import like this
//service.test.js
const { getRegisteredUser } = require('../../modules/bankUser/model/getRegisteredUser');
jest.mock('../../modules/bankUser/model/getRegisteredUser');
getRegisteredUser.mockImplementationOnce(() => ({ getRegisteredUser: () => mockObject }));
EDIT
//getRegisteredUser.js
const { getConnection } = require('../../../global/connection');
const getRegisteredUser = async (userName, cpf) => {
const db = await getConnection('Data-Base');
const res = await db.collection('Collection')
.findOne({ userName, cpf });
return res;
};
module.exports = { getRegisteredUser };
The example you are pointing [https://jestjs.io/docs/es6-class-mocks#replacing-the-mock-using-mockimplementation-or-mockimplementationonce][1]
is related to the default export but I think you are using named export in your code.
There are multiple ways to do so
One Way
const get = require('../../modules/bankUser/model/getRegisteredUser');
jest.mock('../../modules/bankUser/model/getRegisteredUser');
get.getRegisteredUser.mockImplementationOnce(() => mockObject);
Second Way
jest.mock('../../modules/bankUser/model/getRegisteredUser', () => ({
...jest.requireActual('../../modules/bankUser/model/getRegisteredUser'),
getRegisteredUser: jest.fn().mockImplementation(() => mockObject),
}))
An Another Way
We can also mock a module using the __mocks__ directory which is inbuilt feature of jest
And at the end never forgot to call clearAllMocks() after each test else it may impact the output of other tests.
afterEach(() => {
jest.clearAllMocks();
});
I am mocking a function in a library (auth0), this way:
jest.mock('#auth0/auth0-react', () => ({
useAuth0: () => {
return {
logout: jest.fn(),
}
}
}));
During my test, how can I expect the logout function to have been called?
Since you are trying to manually mock a node_module, you need to create a mock module file. Manual mocks are defined by writing a module in a mocks/ subdirectory immediately adjacent to the module. In your case the mock should be placed in the __mocks__ directory adjacent to node_modules.
root/__mocks__/#auth0/auth0-react.js
'use strict';
const handleRedirectCallback = jest.fn(() => ({ appState: {} }));
const buildLogoutUrl = jest.fn();
const buildAuthorizeUrl = jest.fn();
const checkSession = jest.fn();
const getTokenSilently = jest.fn();
const getTokenWithPopup = jest.fn();
const getUser = jest.fn();
const getIdTokenClaims = jest.fn();
const isAuthenticated = jest.fn(() => false);
const loginWithPopup = jest.fn();
const loginWithRedirect = jest.fn();
const logout = jest.fn();
export const Auth0Client = jest.fn(() => {
return {
buildAuthorizeUrl,
buildLogoutUrl,
checkSession,
handleRedirectCallback,
getTokenSilently,
getTokenWithPopup,
getUser,
getIdTokenClaims,
isAuthenticated,
loginWithPopup,
loginWithRedirect,
logout,
};
});
test.ts (in case you are writing test in typescript)
import { mocked } from 'ts-jest/utils';
const clientMock = mocked(new Auth0Client({ client_id: '', domain: '' }));
describe('auth0 test', () => {
it('should check logout is called', async () => {
await my_logout_function();
expect(clientMock.logout).toHaveBeenCalled();
});
});
test.js (in case you are writing tests in javascript)
const clientMock = new Auth0Client({ client_id: '', domain: '' });
describe('auth0 test', () => {
it('should check logout is called', async () => {
await my_logout_function();
expect(clientMock.logout).toHaveBeenCalled();
});
});
I'm testing a cloud function named myCloudFn in my functions/send.js module. My tests are in functions/test/send.test.js:
// send.js
const { getCompareDate } = require('../utils.js');
async function myCloudFn(myTestDate) {
const compareDate = await getCompareDate(argToTest);
const isOlder = myTestDate < compareDate;
return isOlder ? 'older' : 'newer';
}
module.exports = { myCloudFn };
// send.test.js
const send = require('../send.js');
jest.mock('../utils', () => ({
getCompareDate: jest.fn(() => new Date('2020-01-31')) // default
.mockResolvedValueOnce(new Date('2020-04-04'))
.mockResolvedValueOnce(new Date('2020-02-02')),
}));
describe('send.js', () => {
it('returns date comparison from myCloudFn()', async () => {
const myTestDate = '2020-03-03';
const returnValues = ['older', 'newer'];
const responsePromises = returnValues.map(() => send.myCloudFn(myTestDate));
const responses = await Promise.all(responsePromises);
expect(responses[0]).toBe(returnValues[0]);
expect(responses[1]).toBe(returnValues[1]);
});
});
The test functions correctly and passes as expected when I mock getCompareDate in this way, but for flexibility, I would rather provide custom input values for getCompareDate inside my tests and not 'globally'. Here's what I've tried:
const mockGetCompareDate = jest.fn();
jest.mock('../utils', () => ({
getCompareDate: mockGetCompareDate,
}));
it('returns date comparison from myCloudFn()', async () => {
mockGetCompareDate
.mockResolvedValueOnce(new Date('2020-04-04'))
.mockResolvedValueOnce(new Date('2020-02-02'));
const myTestDate = '2020-03-03';
const returnValues = ['older', 'newer'];
const responsePromises = returnValues.map(() => send.myCloudFn(myTestDate));
const responses = await Promise.all(responsePromises);
expect(responses[0]).toBe(returnValues[0]);
expect(responses[1]).toBe(returnValues[1]);
});
This method, however, is not working and throws an error:
ReferenceError: Cannot access 'mockGetCompareDate' before initialization
I've used this method with other tests as noted in the solution in this question, but I am not seeing similar results here. What am I missing?
Jest is hoisting the mocked function to the top of the module, and hence throws this error. The mock should instead be used right before you run the test. Further reading.
Try this:
const { getCompareDate } = require('../utils.js');
const mockGetCompareDate = jest.fn(() => new Date('2020-01-31'));
jest.mock('../utils.js', () => ({
__esModule: true,
getCompareDate: jest.fn(),
default: jest.fn()
}));
beforeAll(() => {
getCompareDate.mockImplementation(mockGetCompareDate);
});
To provide custom values do as you did before, when initialising the mock function. Source
Like this:
const mockGetCompareDate = jest.fn()
.mockResolvedValueOnce(new Date('2020-04-04'))
.mockResolvedValueOnce(new Date('2020-02-02'));
Or do as you did before inside the test. Source
Like this:
it('returns date comparison from myCloudFn()', async () => {
mockGetCompareDate
.mockResolvedValueOnce(new Date('2020-04-04'))
.mockResolvedValueOnce(new Date('2020-02-02'));
I have been trying this for a while and i couldn't find the solution.
I tried a lot of solutions (__ mock __ folder, mockimplementation, mock and more) but i always had the same error client.mockImplementation is not a function
//restclient.js
module.exports = (cfg) => new Client(config);
// api.js
const client = require('restclient');
module.exports.doRequest = () => {
const request = client();
const config = {};
request.get('/path/to/request', config)
.then(result => console.log(result))
}
//api.tests.js
const client = require('restclient');
const api = require('./api');
jest.mock('restclient', () => () => ({
get: jest.fn(),
}));
describe('testing API', () => {
test('test then', async () => {
try {
restclient.mockImplementation(() => () => ({
get: (url, config) => 'Hi! I\'m mocked',
}));
const result = await api.doRequest();
console.log('result', result);
} catch (e) {
console.log('eee', e);
}
});
});
I could not found the solution, I think I can't mock the const request = restclient() part but i dont know why!!
You were missing to mock the constructor.
This mocks the constructor of restclient
jest.mock('restclient', ()=> jest.fn().mockImplementation(() => {
/** here you can create and return a mock of request **/
}));
I write a test with jest to test one of my middleware.
const asyncAll = (req, res, next) => {
const queue = [
service.exchangeLongTimeToken(req),
service.retrieveUserInfo(req),
];
Promise.all(queue).then((values) => {
res.locals.auth = values[0];
res.locals.user = values[1];
next();
}).catch((err) => {
next(err)
});
};
The test file is like this:
const httpMocks = require('node-mocks-http');
const testData = require('../../testdata/data.json');
describe('Test asyncAll', () => {
let spy1 = {};
let spy2 = {};
const mockNext = jest.fn();
afterEach(() => {
mockNext.mockReset();
spy1.mockRestore();
spy2.mockRestore();
});
test('Should call next() with no error when no error with 2 requests', () => {
spy1 = jest.spyOn(service, 'exchangeLongTimeToken').mockImplementation((url) => {
return Promise.resolve(testData.fbLongTimeToken);
});
spy2 = jest.spyOn(service, 'retrieveUserInfo').mockImplementation((url) => {
return Promise.resolve(testData.fbUserInfo);
});
const request = httpMocks.createRequest();
const response = httpMocks.createResponse();
asyncAll(request, response, mockNext);
expect(spy1).toBeCalled();
expect(spy2).toBeCalled();
expect(mockNext).toBeCalled();
expect(mockNext).toBeCalledWith();
expect(mockNext.mock.calls.length).toBe(1);
});
}
The error is like this:
Error: expect(jest.fn()).toBeCalled()
Expected mock function to have been called.
at Object.<anonymous> (tests/backend/unit/fblogin/asyncAll.test.js:39:26)
Which reflects the line:
expect(mockNext).toBeCalled();
Why it doesn't get called?
I read the documents about jest, it says I need to return the promise in order to test the value. But the asyncAll() doesn't return a promise, instead, it consumes a promise, how to deal with this?
You have to notify Jest about the promises you create in the test, have a look at the docs on this topic:
test('Should call next() with no error when no error with 2 requests', async() => {
const p1 = Promise.resolve(testData.fbLongTimeToken);
const p2 = Promise.resolve(testData.fbUserInfo);
spy1 = jest.spyOn(service, 'exchangeLongTimeToken').mockImplementation((url) => {
return p1
});
spy2 = jest.spyOn(service, 'retrieveUserInfo').mockImplementation((url) => {
return p2
});
const request = httpMocks.createRequest();
const response = httpMocks.createResponse();
asyncAll(request, response, mockNext);
await Promise.all([p1,p2])
expect(spy1).toBeCalled();
expect(spy2).toBeCalled();
expect(mockNext).toBeCalled();
expect(mockNext).toBeCalledWith();
expect(mockNext.mock.calls.length).toBe(1);
});