How to clear Jest mock implementation for next tests? - javascript

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

Related

Jest SpyOn - Actual Method is Called, Instead of the Mocked Implementation

I've searched high and low to try and get my Jest test to work but have gotten nowhere in my search.
I've got a function (createRoom) that I want to unit test and I want to mock a method (gameFactory.createNewGameWithInitialPlayer()) call.
GameService.ts
const GameService = (games: {[index: string]: Game}) => {
const playerFactory = PlayerFactory()
const gameFactory = new GameFactory()
const createRoom = ({name, device, socketID}: {name: string, device: string, socketID: string}): RoomResponse => {
const player = playerFactory.createNewPlayer(name, device, socketID)
if (player && player.id) {
const game: Game|undefined = gameFactory.createNewGameWithInitialPlayer(player)
...
}
...
}
GameFactory.ts
export class GameFactory {
createNewGameWithInitialPlayer = (player: Player): Game|undefined => {
const game = new Game()
game.spectators[player.id as any as number] = player
return game
}
}
GameService.test.ts
import * as gameFactory from '../Factories/GameFactory'
describe('Testing Game Service', () => {
test('createRoom', () => {
jest.spyOn(gameFactory, 'GameFactory').mockReturnValue({ createNewGameWithInitialPlayer: jest.fn().mockReturnValue(undefined)})
const response: RoomResponse = gameService.createRoom({
name: 'Player 1',
device: DevicesEnum.ios,
socketID: 'some-socket-id'
})
...
}
...
}
In GameService.test.ts I am mocking the return value of the createNewGameWithInitialPlayer method call. However, when I run the test, the actual implementation of it runs, instead of my mocked version. For this test in particular, I want the createNewGameWithInitialPlayer method to return undefined, but that does not happen, it appears to be calling the actual method implementation.
If you want to override the createNewGameWithInitialPlayer and return what you want then, you have to mock the import of GameFactory class in your test.
// Here you are creating your mock and saying the default return Game object
const mockCreateNewGameWithInitialPlayer = jest.fn().mockImplementation(() => new Game());
// Here you say to jest that any file who wants to import "GameFactory"
// will import this fake class
jest.mock('rootofGameFactory/GameFactory', () => ({
GameFactory: function(){
return {
// and when any file wants to execute this method, will execute my mock
createNewGameWithInitialPlayer: mockCreateNewGameWithInitialPlayer
}
}
}))
describe('Testing Game Service', () => {
test('createRoom', () => {
const response: RoomResponse = gameService.createRoom({
name: 'Player 1',
device: DevicesEnum.ios,
socketID: 'some-socket-id'
})
...
}
...
}
If you want to change the return object of your mocked method, you have to do it like this...
test('createRoom 2', () => {
//Here you say to jest, just ONCE (for this test) return an instance of Game2
mockCreateNewGameWithInitialPlayer.mockImplementationOnce(() => new Game2())
const response: RoomResponse = gameService.createRoom({
name: 'Player 1',
device: DevicesEnum.ios,
socketID: 'some-socket-id'
})
...
}
The jest documentation mentions
By default, jest.spyOn also calls the spied method. This is different behavior from most other test libraries. If you want to overwrite the original function, you can use jest.spyOn(object, methodName).mockImplementation(() => customImplementation) or jest.replaceProperty(object, methodName, jest.fn(() => customImplementation));
So you could do something like
jest.spyOn(gameFactory, 'GameFactory').mockImplementation(() => { return undefined })

jest mock function dont work in my test case

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

Jest mocking a module with module pattern and having unique functionality on each test

I'm having trouble having a unique implementation of search from this module pattern setup I have (MyConnection.js) - here's similar to what I have:
// MyConnection.js
const MyConnection = () => {
const search = async (q) => {
//...some functionality
};
return {
search,
};
};
//JEST TESTS
const MyConnection = require('../MyConnection')
// this works - but it sets the search implementation
// for the whole test file I believe
jest.mock('../MyConnection', () => {
return jest.fn(()=>{
search: ()=> ['mocked', 'fn', 'results']
})
})
//the jest tests - I want different
// implementations of search in each test
describe('connection tests', ()=>{
it('test one', ()=>{
//Not sure if its something like this to set 'search' for each test? This doesn't work as is
MyConnection.search.mockImplementation((q)=>{`You searched ${q}`})
})
it('test two', ()=>{
MyConnection.search.mockImplementation((q)={q.length>32? 'a': 'b' }})
})
})
How can I get unique Jest mock implementations of that search function for each test?
You should make sure that mock MyConnection always returns the same connection object in your test file and the module you want to test.
MyConnection.js:
const MyConnection = () => {
const search = async (q) => {};
return {
search,
};
};
module.exports = MyConnection;
main.js:
const MyConnection = require('./MyConnection');
async function main(q) {
const conn = MyConnection();
return conn.search(q);
}
module.exports = main;
main.test.js:
const MyConnection = require('./MyConnection');
const main = require('./main');
jest.mock('./MyConnection', () => {
console.log('MyConnection module gets mocked');
const conn = { search: jest.fn() };
return jest.fn(() => conn);
});
const mConn = MyConnection();
describe('connection tests', () => {
it('test one', async () => {
mConn.search.mockImplementation((q) => {
return `You searched ${q}`;
});
const actual = await main('teresa teng');
expect(actual).toBe('You searched teresa teng');
});
it('test two', async () => {
mConn.search.mockImplementation((q) => {
return q.length > 32 ? 'a' : 'b';
});
const actual = await main('_');
expect(actual).toBe('b');
});
});
test result:
PASS examples/70132655/main.test.js (10.454 s)
connection tests
✓ test one (2 ms)
✓ test two (1 ms)
console.log
MyConnection module gets mocked
at examples/70132655/main.test.js:5:11
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 10.903 s
You can't mock it like this:
jest.mock('./MyConnection', () => {
console.log('MyConnection module gets mocked');
return jest.fn(() => { search: jest.fn() });
});
Why? Because every time you call the MyConnection() function in the test file or the file you want to test. It will return a new mock object({ search: jest.fn() }), the mock objects in file under test and test case are not same.

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 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