jest mock function dont work in my test case - javascript

when i import modules, i mock them with jest.mock() api :
import analytics, { fakeAnalyticsApi } from "../../middleware/analytics";
jest.mock("../../middleware/analytics", () => {
const orginalModules = jest.requireActual("../../middleware/analytics");
return {
__esModule: true,
...orginalModules,
fakeAnalyticsApi: jest.fn(() => Promise.resolve("success")),
};
});
then when i call "fakeAnalyticsApi" function Its original version is executed.
full test file:
import analytics, { fakeAnalyticsApi } from "../../middleware/analytics";
jest.mock("../../middleware/analytics", () => {
const orginalModules = jest.requireActual("../../middleware/analytics");
return {
__esModule: true,
...orginalModules,
fakeAnalyticsApi: jest.fn(() => Promise.resolve("success")),
};
});
const create = () => {
const store = {
getState: jest.fn(() => {}),
dispatch: jest.fn(),
};
const next = jest.fn();
const invoke = (action) => analytics(store)(next)(action);
return { store, next, invoke };
};
// suite test
describe("analytics middleware", () => {
test("should pass on irrelevant keys", () => {
const { next, invoke } = create();
const action = { type: "IRREVELANT" };
invoke(action);
expect(next).toHaveBeenCalledWith(action);
expect(fakeAnalyticsApi).not.toHaveBeenCalled();
});
test("should make an analytics API call", () => {
const { next, invoke } = create();
const action = {
type: "REVELANT",
meta: {
analytics: {
event: "REVELANT",
data: { extra: "stuff" },
},
},
};
invoke(action);
expect(fakeAnalyticsApi).toHaveBeenCalled();
expect(next).toHaveBeenCalledWith(action);
});
});
and I get the following error:
● analytics middleware › should make an analytics API call
expect(jest.fn()).toHaveBeenCalled()
Expected number of calls: >= 1
Received number of calls: 0
39 | };
40 | invoke(action);
> 41 | expect(fakeAnalyticsApi).toHaveBeenCalled();
| ^
42 | expect(next).toHaveBeenCalledWith(action);
43 | });
44 | });
I log a statement in orginal version function of "fakeAnalyticsApi", it seems orginal version was call because the console log the statement

I found the problem
It was not related to the mock function in the jest
If an ES6 module directly exports two functions (not within a class, object, etc., just directly exports the functions like in the question) and one directly calls the other, then that call cannot be mocked.
i implemented fakeAnalyticsApi inside the analytics.js module and exported it to mock it for test purpose , but it doesn`t work.
more details: https://stackoverflow.com/a/55193363/17615078

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');

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

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();
});

Testing to see if a function is called with Jest and Typescript, and ts-jest?

So I am trying to test this code
src/helpers/CommentHelper.ts:
export default class CommentHelper {
gitApiObject: GitApi.IGitApi ;
constructor(gitApiObject: GitApi.IGitApi)
{
this.gitApiObject = gitApiObject;
}
async postComment(commentContent: string, repoId: string, pullRequestId: number): Promise<any> {
const comment: GitInterfaces.Comment = <GitInterfaces.Comment>{content: commentContent};
const newCommentThread: GitInterfaces.GitPullRequestCommentThread = <GitInterfaces.GitPullRequestCommentThread>{comments: [comment]}
await this.gitApiObject.createThread(newCommentThread, repoId, pullRequestId);
}
}
Here the tests:
import CommentHelper from "../helpers/CommentHelper";
import { mocked } from 'ts-jest/utils';
import { GitApi, IGitApi } from "azure-devops-node-api/GitApi";
jest.mock('../helpers/CommentHelper', () => {
return {
default: jest.fn().mockImplementation(() => {})
};
});
describe("CommentHelper Tests", () => {
const mockedGitApi = mocked(GitApi, true);
beforeEach(() => {
mockedGitApi.mockClear();
});
it("Check to see if the gitApiObject is called properly", () => {
const commentHelper = new CommentHelper(<any>mockedGitApi);
const spy = jest.spyOn(GitApi.prototype ,'createThread')
commentHelper.postComment("", "", 0);
expect(spy).toHaveBeenCalled();
})
})
This is the error:
TypeError: commentHelper.postComment is not a function
23 | const commentHelper = new CommentHelper(<any>mockedGitApi);
24 | const spy = jest.spyOn(GitApi.prototype ,'createThread')
> 25 | commentHelper.postComment("", "", 0);
| ^
26 | expect(spy).toHaveBeenCalled();
27 | })
28 |
Right now we're early in the project so the tests are extremely simple. We just want to make sure gitApiObject/createThread is called. How can I achieve this without explicitly mocking out the postComment function?
Thanks! :)
So if I get your code right, you're currently mocking the default export of your CommentHelper as a function.
When accessing postComment you will get the response of your mock back which is currently not defined.
As I see in the other things you have provided in your example test case you want to test if GitAPI was called. In this case, you can't mock CommentHelper since then there is no possibility for GitApi to be called.
If you want to mock CommentHelper you have to return
jest.mock('../helpers/CommentHelper', () => {
return {
default: jest.fn().mockImplementation(() => ({
postComment:jest.fn()
}))
};
});
if you just want to spy on GitAPI your good to go. If you don't want GitAPI to be called add .mockImplementation after your spyOn.
Hope this helps!

How to clear Jest mock implementation for next tests?

I'm setting up Jest to test a typescript application.
How do I clear a mocked function and restore the original implementation for other tests?
To mock the function I've used: jest.fn().mockImplementationOnce()
So far I've tried jest.clearAll() / resetModules() / resetAllMocks() in beforeEach as well as afterEach without any success.
app.test.ts
import App from './app';
import { DbService } from './lib/dbService';
describe('App', () => {
let dbService: DbService;
let app: App;
beforeEach(() => {
jest.clearAllMocks();
dbService = new DbService();
app = new App();
});
describe('getUsers', () => {
it('Should get an array users #1', () => {
expect(app).toBeInstanceOf(App);
const allUsers = app.getAllUsers();
expect(allUsers[0].id).toBeDefined();
});
it('should return an error #2', () => {
DbService.prototype.getAllUsers =
jest.fn().mockImplementationOnce(() => {
return new Error('No connection to DB');
});
expect(app.getAllUsers()).toEqual(new Error('No connection to DB'));
});
it('Should get an array users #3', () => {
expect(app).toBeInstanceOf(App);
const allUsers = app.getAllUsers();
expect(allUsers[0].id).toBeDefined();
});
});
});
app.ts
import { DbService } from './lib/dbService';
export default class App {
private dbService: DbService;
constructor() {
this.dbService = new DbService();
}
getAllUsers() {
return this.dbService.getAllUsers();
}
}
lib/dbService.ts
let instance: DbService;
export class DbService {
constructor() {
if (!instance) {
instance = this;
}
return instance;
}
getAllUsers() {
return [
{id: 1, username: 'john'},
{id: 2, username: 'bill'}
]
}
}
I expect test #3 to pass like test #1, but it actually fails with the following error:
FAIL src/app.test.ts
App
getUsers
√ Should get an array users #1 (3ms)
√ should return an error #2 (1ms)
× Should get an array users #3 (1ms)
● App › getUsers › Should get an array users #3
TypeError: Cannot read property '0' of undefined
31 | expect(app).toBeInstanceOf(App);
32 | const allUsers = app.getAllUsers();
> 33 | expect(allUsers[0].id).toBeDefined();
| ^
34 | });
35 | });
36 | });
I'm not sure if this is the jest way of achieving this but I think you could save the original method implementation in a variable and re-set the method after every test in case it was mocked in a test.
E.g.
describe('App', () => {
let dbService: DbService;
let app: App;
let originalGetAllUsersFn = DbService.prototype.getAllUsers;
//...
afterEach(() => {
// restore mocked method
DbService.prototype.getAllUsers = originalGetAllUsersFn;
});
});
Jest has setup/teardown functions:
https://flaviocopes.com/jest/#setup
To do something once before all the tests run, use the beforeAll() function:
beforeAll(() => {
//do something
})
To perform something before each test runs, use beforeEach():
beforeEach(() => {
//do something
})
Teardown
Just as you can do with setup, you can also perform something after each test runs:
afterEach(() => {
//do something
})
and after all tests end:
afterAll(() => {
//do something
})
Do the mocking in the setup functions and restore in the teardown

How do change implementation of a Jest mock for a certain test

I've create a file in my test/__mocks__ folder where I mock a npm module. And the link of this file is add to my jest setup. All work great this let me test it pretty nicely. But now for a certain test I need to change the return value from this one. How can I achieve this?
I try to unMock plus setMock etc. But nothing work.
// test/__mocks__/touchId.ts
jest.mock('react-native-touch-id', () => {
return {
isSupported: jest.fn(() => Promise.resolve(true)),
authenticate: jest.fn(() => Promise.resolve(true)),
};
});
And my test
it('should not navigate to main if touch id return false', async () => {
jest.setMock('react-native-touch-id', {
authenticate: jest.fn(() => Promise.resolve(false)),
});
const pinCreation = new PinCreationStore();
const spy = jest.spyOn(NavigationServices, 'navigate');
spy.mockReset();
await pinCreation.verifyUser();
expect(spy).toHaveBeenCalledTimes(0);
spy.mockRestore();
});
Here I still get true so my test crash.
You can use jest.mock() without creating __mocks__ folder.
For example:
react-native-touch-id.ts, I simulate this module in order to keep it simple. You can replace it with real npm module.
const touchId = {
isSupported() {
return false;
},
authenticate() {
return false;
}
};
export default touchId;
react-native-touch-id.spec.ts:
import touchId from './react-native-touch-id';
jest.mock('./react-native-touch-id', () => {
return {
isSupported: jest.fn(() => Promise.resolve(true)),
authenticate: jest.fn(() => Promise.resolve(true))
};
});
describe('react-native-touch-id', () => {
it('t1', () => {
expect(touchId.isSupported()).toBeTruthy();
expect(touchId.authenticate()).toBeTruthy();
});
it('t2', () => {
(touchId.isSupported as jest.MockedFunction<typeof touchId.isSupported>).mockReturnValueOnce(false);
(touchId.authenticate as jest.MockedFunction<typeof touchId.authenticate>).mockReturnValueOnce(false);
expect(touchId.isSupported()).toBeFalsy();
expect(touchId.authenticate()).toBeFalsy();
});
it('t3', () => {
(touchId.isSupported as jest.MockedFunction<typeof touchId.isSupported>).mockReturnValueOnce(true);
(touchId.authenticate as jest.MockedFunction<typeof touchId.authenticate>).mockReturnValueOnce(false);
expect(touchId.isSupported()).toBeTruthy();
expect(touchId.authenticate()).toBeFalsy();
});
});
As you can see, after you mock react-native-touch-id module, you need to import it and mocked again when you want these two methods to have different values. These mocked values will be used in other modules which import and use isSupported and authenticate methods of react-native-touch-id module.
Unit test result:
PASS src/stackoverflow/52172531/react-native-touch-id.spec.ts
react-native-touch-id
✓ t1 (5ms)
✓ t2 (1ms)
✓ t3
Test Suites: 1 passed, 1 total
Tests: 3 passed, 3 total
Snapshots: 0 total
Time: 4.065s

Categories