How To Spy On An Exported and Mocked Function In Jest - node.js

I am trying to mock a module (which has an exported function[and this is also mocked]).
I would like to spy on the exported function to check that it was called with something.
This is my code...
import { addNewPaymentMethod } from '../src/service'
jest.mock('../src/service', () => ({
addNewPaymentMethod : (paymentMethodInfoModel) => {
let responseFromApi = {responseStatus:{name:'blah'}};
return Promise.resolve(responseFromApi);
}
}))
import { createNewPaymentMethod } from '../src/actions/paymentMethod'
test('test expect', () => {
createNewPaymentMethod({owNickName:'nName',id:22})();
//this is the bit I don't know how to do
//...
jest.spyOn(addNewPaymentMethod);
expect(addNewPaymentMethod).toBeCalledWith({some:object});
});

You can define the mock using jest.fn().mockResolvedValue(value)
jest.mock('../src/service', () => ({
addNewPaymentMethod : jest.fn().mockResolvedValue({
responseStatus: {
name:'blah'
}
})
}))
and then since it's a jest.fn() you don't need to spyOn it :)
but note that since it's async (uses promises) you either have to chain then to the method (and use return):
it('should have been called with data', () => {
const data = {};
return service.methodWhichCallsDependencysaddNewPaymentMethod(data).then(() => {
return expect(addNewPaymentMethod).toHaveBeenCalledWith(data);
});
});
or use async/await:
it('should have been called with data', async () => {
const data = {};
await service.methodWhichCallsDependencysaddNewPaymentMethod(data);
expect(addNewPaymentMethod).toHaveBeenCalledWith(data);
});
I've set up a working example

Related

jest test async function recieving undefined

Hi I'm trying to test a post route that make loots of operations using Jest.
I'm trying to mock a class that makes a http call to another API. The problem is the response is always undefined and I have no idea whats wrong.
This is my mock comunicante-service.js (located in root/mock):
module.exports = {
getConfig: jest.fn(async () => {
Promise.resolve(dadosContaConfig);
}),
sendMessage: jest.fn(async () => {
Promise.resolve('ok');
}),
};
And that's the test
jest.mock('../src/services/comunicante-service');
describe('test /message', () => {
it('no token', async () => {
const res1 = await request(app).post('/message').set(config);
expect(res1.statusCode).toEqual(400);
});
inside of the code that handle the /result there's a call to
const aux = await comunicanteService.getConfig(conta_id);
It looks like you may have forgotten to return something from the mock service. You could try:
module.exports = {
getConfig: jest.fn(async () => {
return Promise.resolve(dadosContaConfig);
}),
sendMessage: jest.fn(async () => {
return Promise.resolve('ok');
}),
};
This can be simplified to:
module.exports = {
getConfig: jest.fn().mockResolvedValue(dadosContaConfig),
sendMessage: jest.fn().mockResolvedValue('ok'),
};

Change jest mock on class for single test

I have an issue where I want to change what a class method returns for a single test while testing a different module. I have the following:
testingModule.test.js
const { testingModuleMethod } = require('../testingModule')
jest.mock('../helperClass', () =>
jest.fn().mockImplementation(() => ({
helperClassMethod: jest.fn()
}))
);
describe('testingModule.js', () => {
describe('testingModuleMethod', () => {
describe('when errors', () => {
const consoleSpy = jest.spyOn(console, 'error');
// SOMETHING NEEDS TO GO HERE TO CHANGE THE jest.mock ON LINE 3
await expect(testingModuleMethod(data)).rejects.toThrow('Error');
expect(consoleSpy).toHaveBeenCalled();
consoleSpy.mockRestore();
});
});
});
testingModule.js
const HelperClass = require('./helperClass');
const testingModuleMethod = async (data, callback) => {
try {
const objectToEvaluate = data.object;
const helperClassInstance = new HelperClass();
await helperClassInstance.helperClassMethod(objectToEvaluate);
log('info', "Success!");
callback(null, {});
} catch(error) {
log('error', 'Something went wrong')
}
};
No matter what I put in there I either get an error with the code (undefined) or it just ignores it and resolves due to the mock at the start. I have tried adding a spy as well as importing the class and using the prototype override.
I'm using node and "jest": "^27.0.6"
I have managed to answer this by doing the following:
Firstly I discovered that to mock a class like that I have to add a jest function into the mock like so:
describe('testingModuleMethod', () => {
describe('when errors', () => {
const consoleSpy = jest.spyOn(console, 'error');
HelperClass.mockImplementation(() => ({
helperClassMethod: jest.fn(() => { throw new Error('Error') })
}));
await expect(testingModuleMethod(data)).rejects.toThrow('Error');
expect(consoleSpy).toHaveBeenCalled();
consoleSpy.mockRestore();
});
});
This also had a knock on effect to the rest of the tests though so I added a beforeEach at the start that looks like:
HelperClass.mockImplementation(
jest.fn().mockImplementation(() => ({
helperClassMethod: jest.fn()
}))
);
Finally I needed to require the class. The overall test looks like this now and works:
const { testingModuleMethod } = require('../testingModule');
const HelperClass = require('./helperClass');
jest.mock('../helperClass', () =>
jest.fn().mockImplementation(() => ({
helperClassMethod: jest.fn()
}))
);
describe('testingModule.js', () => {
beforeEach(() => {
HelperClass.mockImplementation(
jest.fn().mockImplementation(() => ({
helperClassMethod: jest.fn()
}))
);
});
describe('testingModuleMethod', () => {
describe('when errors', () => {
const consoleSpy = jest.spyOn(console, 'error');
HelperClass.mockImplementation(() => ({
helperClassMethod: jest.fn(() => { throw new Error('Error') })
}));
await expect(testingModuleMethod(data)).rejects.toThrow('Error');
expect(consoleSpy).toHaveBeenCalled();
consoleSpy.mockRestore();
});
});
});

mocking aws-sdk-sns with jest.mock

I have the following code (t
// mock sns
const testMock = jest.fn().mockImplementation(() => {
return {
promise: () => jest.fn()
};
});
jest.mock('aws-sdk/clients/sns', () => {
return jest.fn().mockImplementation(() => {
return { publish: testMock };
});
});
Now when I run this I get the following error
ReferenceError: Cannot access 'testMock' before initialization
Could it have something to do with jest hoisting the mock or something like that ?
Further to do this, if I move the testmock insider the scope of mock then it works fine.
// this works
jest.mock('aws-sdk/clients/sns', () => {
const testMock = jest.fn().mockImplementation(() => {
return {
promise: () => jest.fn()
};
});
return jest.fn().mockImplementation(() => {
return { publish: testMock };
});
});
However this beats the purpose since I cannot check the calls or anything.

Mock a custom event emitter with jest

I want to assert that emit from the EventEmitter class was called with specific parameters by using Jest. I have a separate file where I create an instance of eventEmitter to be used, and on the other class I import it and at some point the event is emitted.
// commonEmitter.ts
const events = require('events');
export const commonEmitter = new events.EventEmitter();
// class.ts
import { commonEmitter } from (..)
export class MyClass {
(...)
method(){
commonEmitter.emit('eventName', { data: true});
}
}
// class.spec.ts
let commonEmitterMock: any
beforeEach(() => {
commonEmitterMock = createMock('emit');
});
it('testMyClass', async () => {
const method = new MyClass().method();
expect(commonEmitterMock).toHaveBeenCalledWith('eventName', { data: true})
}
With this implementation the emit event is never called..
Cant figure out why, any idea?
To test different branches of your http request events without giving up on a over-engineered code you can do the follow.
This is a stub version of the function I intend to test using Jest:
function myRequest(resolve, reject) {
http.request(url, options, (res: IncomingMessage) => {
response.on('data', (chunk) => {
// On data event code
})
response.on('end', () => {
// On end event code
resolve()
})
response.on('error', (err) => {
reject(err)
})
}
}
Firstly, we need to mock the http library an overwrite the request implementation to manually trigger the callback and inject our mocked response object:
...
const mockRes = {
write: jest.fn(),
on: jest.fn(),
end: jest.fn()
}
jest.mock('http', () => ({
request: jest.fn().mockImplementation((url, options, cb) => {
cb(mockRes)
})
})
Then, each of our jest test unit, we manually trigger the callbacks on each of the events we desire to test passing data to each of the specific callbacks:
it('should call request callback and reject for invalid content response', async () => {
const resolve = jest.fn()
const reject = jest.fn()
mockRes.on.mockImplementation((event, cb) => {
if (event === 'end') {
cb()
} else if (event === 'data') {
cb(new Error('invalid_json_string'))
}
})
// #ts-ignore
myRequest(resolve, reject)
// #ts-ignore
expect(mockRes.on).toHaveBeenCalledWith('data', expect.any(Function))
expect(mockRes.on).toHaveBeenCalledWith('end', expect.any(Function))
expect(reject).toHaveBeenCalledWith(expect.any(Error))
})
It is better to inject the dependency to make your class more testable instead of importing it. So your class will look like
export class MyClass {
constructor(commonEmitter) {
this.commonEmitter_ = commonEmitter;
}
method(){
this.commonEmitter_.emit('eventName', { data: true});
}
}
And then your test file could be
let commonEmitterMock: any
beforeEach(() => {
commonEmitterMock = createMock('emit');
});
it('testMyClass', async () => {
const method = new MyClass(commonEmitterMock).method();
expect(commonEmitterMock).toHaveBeenCalledWith('eventName', { data: true})
}

how do I test node.js template render in jest

This is one of my routes.
const actor = await Actor.findById(req.params.id);
if(!actor) throw new Error("Actor not found");
res.render('admin/actors/edit_actor',{actor:actor});
The thing is I don't know how to test if valid actor gets returned because of render function.
================================================================
If I write the following
const actor = await Actor.findById(req.params.id);
if(!actor) throw new Error("Actor not found");
res.send({actor:actor});
I know how to test this because this actor would be in body parameters. such as:
//test
const res = await request(server).get('/actor/2');
res.body is the same as actor
So questions:
1) how do I test the first example which renders some view?
2) first example to test there's an integration test needed. and for the second example, we should write functional test. Am I right?
In an unit test you're supposed to mock your dependencies, so if you're testing your controller you should mock the req and res objects as well as the model. For example
Implementation
import Actor from '../model/Actor';
const controller = (req, res) => {
const actor = await Actor.findById(req.params.id);
if(!actor) throw new Error("Actor not found");
res.render('admin/actors/edit_actor',{actor:actor});
}
Unit Test
import Actor from '../model/Actor';
jest.mock('../model/Actor');
describe('controller', () => {
const req = {
params: { id: 101 }
};
const res. = {
render: jest.fn()
};
beforeAll(() => {
Actor.findById.mockClear();
controller(req, res);
});
describe('returning an actor', () => {
beforeAll(() => {
res.render.mockClear();
Actor.findById.mockResolvedValue({
name: "Some Actor"
});
controller(req, res);
});
it('should get actor by id', () => {
expect(Actor.findById).toHaveBeenCalledWith(101);
});
it('should call res.render', () => {
expect(res.render).toHaveBeenCalledWith('admin/actors/edit_actor', { actor });
})
});
describe('not returning an actor', () => {
beforeAll(() => {
res.render.mockClear();
Actor.findById.mockResolvedValue(undefined);
controller(req, res);
});
it('should throw an Error', () => {
expect(() => controller(req, res)).toThrow(Error);
});
it('should not call res.render', () => {
expect(res.render).not.toHaveBeenCalled();
});
});
});

Resources