How to test if function passed as parameter was called in Jest - javascript

I have a function that receives another function as an argument. I would like to make sure it was called properly.
Function to be tested:
const loadNamespaces = (setNamespaces) => {
namespaceAPI.getNamespaces().then(namespaces => {
setNamespaces(namespaces);
});
}
My main goal here was to assert mockSetNamespaces was called.
I was able to mock and assert namespaceAPI.getNamespaces was called by using jest.spyOn method, but that didn't work for asserting if mockSetNamespaces was called:
test("loadNamespaces", () => {
const mockSetNamespaces = jest.fn();
const mockNamespaces = [
{ endpoint: "mock namespace 1", rooms: [] },
];
jest.spyOn(namespaceAPI, "getNamespaces").mockImplementation(() => {
return new Promise((resolve) => {
resolve(mockNamespaces);
});
});
SocketIOActions.loadNamespaces(mockSetNamespaces);
expect(namespaceAPI.getNamespaces).toHaveBeenCalled();
expect(mockSetNamespaces).toHaveBeenCalled();
});
Error message received from Jest:
● loadNamespaces
expect(jest.fn()).toHaveBeenCalled()
Expected number of calls: >= 1
Received number of calls: 0
I've also tried to add setNamespaces to an object, so jest.spyOn method could be used, but also didn't assert method was called:
test("loadNamespaces", () => {
const mockObject = {
mockSetNamespaces: jest.fn(),
};
const mockNamespaces = [
{ endpoint: "mock namespace 1", rooms: [] },
];
jest.spyOn(namespaceAPI, "getNamespaces").mockImplementation(() => {
return new Promise((resolve) => {
resolve(mockNamespaces);
});
});
jest.spyOn(mockObject, "mockSetNamespaces").mockImplementation(() => {
console.log("Hello from spy function");
});
SocketIOActions.loadNamespaces(mockObject.mockSetNamespaces);
expect(namespaceAPI.getNamespaces).toHaveBeenCalled();
expect(mockObject.mockSetNamespaces).toHaveBeenCalled();
});
Proof that mock function was actually called:
console.log
Hello from spy function
Is this the expected behavior from Jest? I would be glad to know if there is a cleaner way to do this.

Using spyOn when you need to mock specific function from the module instead of mocking all.
I would do in this way.
// this will help you auto mock all namespaceAPI function. If you just need to mock "getNamespaces" then you stick with spyOn
jest.mock('namespaceAPI')
test("loadNamespaces", () => {
// you can directly mock implementation in jest function, dont need to spy it again.
const mockSetNamespaces = jest.fn().mockImplementation(() => {
console.log("Hello from spy function");
});
SocketIOActions.loadNamespaces(mockSetNamespaces);
expect(namespaceAPI.getNamespaces).toHaveBeenCalled();
expect(mockSetNamespaces).toHaveBeenCalled();
});

Related

How to test properly mobX 'reaction' state in jest

I could not write unit tests with a jest for my class function that uses 'reaction' that comes from the 'mobx' library.
I was trying to test the init() function shown below.
async init() {
reaction(
() => this.repositoryManager.selectedClient?.repositoryMeta,
(repositoryMeta) => {
clearInterval(this.incidentsTimer);
clearInterval(this.anomaliesTimer);
if (!repositoryMeta) return;
this.getOpenIncidents();
this.getOpenAnomalies();
this.incidentsTimer = setInterval(this.getOpenIncidents, 60 * 1000);
this.anomaliesTimer = setInterval(this.getOpenAnomalies, 60 * 1000);
}
);
}
To achieve that, I have used jest mock like this :
import { makeAutoObservable, reaction } from 'mobx';
jest.mock('mobx', () => ({
makeAutoObservable: jest.fn(),
reaction: jest.fn(),
}));
This test passes without a problem.
test('should call the reaction method with the correct arguments', async () => {
await newRelicManager.init();
expect(reaction).toHaveBeenCalledWith(expect.any(Function), expect.any(Function));
});
But if I want to test my logic, that is inside a reaction like this.
test('should call the getOpenIncidents and getOpenAnomalies methods', async () => {
// Arrange
const getOpenIncidentsSpy = jest.spyOn(newRelicManager, 'getOpenIncidents');
const getOpenAnomaliesSpy = jest.spyOn(newRelicManager, 'getOpenAnomalies');
const setIntervalSpy = jest.spyOn(global, 'setInterval');
newRelicManager.repositoryManager = {
selectedClient: {
repositoryMeta: {
name: 'some-repository-name',
},
},
} as any;
// Act
await newRelicManager.init();
// Assert
expect(getOpenIncidentsSpy).toHaveBeenCalled();
expect(getOpenAnomaliesSpy).toHaveBeenCalled();
expect(setIntervalSpy).toHaveBeenCalledTimes(2);
expect(newRelicManager.incidentsTimer).toBeDefined();
expect(newRelicManager.anomaliesTimer).toBeDefined();
});
It throws:
Error: expect(jest.fn()).toHaveBeenCalled()
Expected number of calls: >= 1
Received number of calls: 0
So even if repositoryMeta is set, it can't simulate inside of the reaction. So what is the best way to test mobx reaction?
Solution:
To trigger the callback function, that is the parameter of the reaction method. jest.spyOn() should be used instead of jest.fn() like this:
makeAutoObservable = jest.spyOn(mobx, 'makeAutoObservable');
reaction = jest.spyOn(mobx, 'reaction');
And it should be triggered in the test like this.
await newRelicManager.init();
reaction.mock.calls[0][1]('repositoryMeta');

JEST - How to check the prop- actions getting called from the function

I am new to the React and Jest and I am trying to test my function where : I have a function :
handleSubmit = (e) => {
this.changeData().then(() => {
const payload = {
somePayload: 'sample'
};
this.props.actions.authenticate(payload);
});
};
and the jest function as :
it('test mock test', () => {
const actionsMock = { authenticate: jest.fn() };
const localWrapper = shallow(<SomeComponent actions={actionsMock} />);
const instance = localWrapper.instance();
jest.spyOn(instance, 'handleSubmit');
instance.forceUpdate();
localWrapper.find('.button').simulate('click');
expect(actionsMock.authenticate).toHaveBeenCalledWith({somePayload: 'sample'});
});
So, In this case, when I click on the button it calls the handlesubmit function and eventually the this.props.actions.authenticate(payload); but when I assert the same in Jest it gives me error that function was never called.
EDIT
As in the comment CJ Pointed: I see that my assertion is getting called even before the promise for changeData resolved. So, How I can I make my assertion wait till the promise gets resolved?

Why is the Jest mock instance empty when mocking a Node.js module?

I'm having some problems with mocking, I've mocked a node module by adding a mocks/ssh2-sftp-client.ts file:
const mockSsh2SftpClient = jest.fn().mockImplementation(() => {
return {
connect: async () => {},
end: async () => {},
on: () => {}
}
})
export default mockSsh2SftpClient
This works, kinda. My tests run correctly using this mock, but in the tests SftpClient.mock.instances[0] is an empty mockConstructor {} object instead of this mock (ie. SftpClient.mock.instances[0].end is undefined). What am I doing wrong?
for reference, my testing code looks like this:
import { ConnectConfig } from 'ssh2'
import SftpClient from 'ssh2-sftp-client'
import { withSftp } from '../sftp'
// Type assertion to make TypeScript happy.
const MockSftpClient = SftpClient as jest.Mock<SftpClient>
describe(withSftp, () => {
const mockConnectionConfig: ConnectConfig = {}
beforeEach(() => {
// Clear all instances and calls to constructor and all methods:
MockSftpClient.mockClear()
})
it('should call the callback after successfully connecting', async () => {
const mockCallback = jest.fn()
// Instantiates SftpClient and calls connect, then the callback, then end.
await withSftp(mockConnectionConfig, mockCallback)
const mockInstance = MockSftpClient.mock.instances
expect(mockCallback).toHaveBeenCalledTimes(1)
expect(MockSftpClient.mock.instances[0].end).toHaveBeenCalledTimes(1)
})
})
The last fails because MockSftpClient.mock.instances[0].end is undefined, where it should be a function.
The mock constructor provided by Jest only records this as the instance so if your mock constructor returns a different object then that object won't be recorded in the instances array.
To get the behavior you are wanting just mock with a standard function and use this:
__mocks__/ssh2-sftp-client.ts
const mockSsh2SftpClient = jest.fn(function() {
this.connect = jest.fn();
this.end = jest.fn();
this.on = jest.fn();
});
export default mockSsh2SftpClient

How would I test a function with an internal promise?

I am currently trying to write integration test to make sure that some specific client cases which have caused issues are not broken with further changes to a script. I am not sure how to properly format the test so that it runs and waits for the function to finish before returning:
Your test suite must contain at least one test.
The system calls the appropriate functions when mocked.
// app.ts
function doThing() {
const data = window.data;
if (!isEmpty(data)) {
firstAsync(value1 => {
return secondAsync(value1);
}).then(value2 => {
someCall();
}).catch(error => {
log(error);
});
} else {
someOtherCall();
}
}
// app.test.ts
someCall = jest.fn();
someOtherCall = jest.fn();
describe('test', () => {
it('should work', () => {
window.data = { value: 'something' };
doThing();
expect(someCall).toHaveBeenCalled();
expect(someOtherCall).not.toHaveBeenCalled();
});
});
TL:DR - How would I write a test to cover a function that has a promise inside of it, but isn't a promise itself?

sinon stub not replacing function

I'm trying to use sinon stub to replace a function that might take along time. But when I run the tests, the test code doesn't seem to be using the sinon stubs.
Here is the code I'm trying to test.
function takeTooLong() {
return returnSomething();
}
function returnSomething() {
return new Promise((resolve) => {
setTimeout(() => {
resolve('ok')
}, 1500)
})
}
module.exports = {
takeTooLong,
returnSomething
}
and this is the test code.
const chai = require('chai')
chai.use(require('chai-string'))
chai.use(require('chai-as-promised'))
const expect = chai.expect
chai.should()
const db = require('./database')
const sinon = require('sinon')
require('sinon-as-promised')
describe('Mock the DB connection', function () {
it('should use stubs for db connection for takeTooLong', function (done) {
const stubbed = sinon.stub(db, 'returnSomething').returns(new Promise((res) => res('kk')));
const result = db.takeTooLong()
result.then((res) => {
expect(res).to.equal('kk')
sinon.assert.calledOnce(stubbed);
stubbed.restore()
done()
}).catch((err) => done(err))
})
I get an assertion error
AssertionError: expected 'ok' to equal 'kk'
+ expected - actual
-ok
+kk
What am I doing wrong? Why isn't the stub being used ? The test framework in Mocha.
Sinon stubs the property of the object, not the function itself.
In your case you are exporting that function within an object.
module.exports = {
takeTooLong,
returnSomething
}
So in order to properly call the function from the object, you need to replace your function call with the reference to the export object like :
function takeTooLong() {
return module.exports.returnSomething();
}
Of course based on your code, you can always refactor it :
var exports = module.exports = {
takeTooLong: function() { return exports.returnSomething() }
returnSomething: function() { /* .. */ }
}
You might want to have a look at Proxyquire to stub/spy directly exported functions.
https://www.npmjs.com/package/proxyquire/

Categories