Jest call default mock from a file - javascript

I have this code on my test:
//app.test.js
jest.mock('notistack', () => ({
useSnackbar: jest.fn(),
}));
jest
.spyOn(notistack, 'useSnackbar')
.mockImplementation(() => ({ enqueueSnackbar }));
How can I move this function to a single file? all of my test contains this same function and I think its redundant to keep copy paste this code. I've tried moving this to other file and make function so I can import it
// helper.js
export function snackbar() {
jest.mock('notistack', () => ({
useSnackbar: jest.fn(),
}));
jest
.spyOn(notistack, 'useSnackbar')
.mockImplementation(() => ({ enqueueSnackbar }));
}
// app.test.js
import {snackbar} from 'helper.js';
snackbar();
// othercode for testing;
but everytime I run it, it always return me Cannot spyOn on a primitive value; undefined given.
how can I call it properly?

have a look at Manual Mocks Guide in Jest documentation.
You can create a folder called __mocks__ and replicate the modules that you want to mock there.
For example, I have a main.js file with:
let { Chance } = require("chance");
let chance = Chance();
function createUser() {
return {
id: chance.guid(),
name: chance.name(),
age: chance.age(),
};
}
module.exports = { createUser };
I create a main.test.js file with:
let { createUser } = require("./main");
test("creates a new user", () => {
let scenario = createUser();
expect(scenario).toMatchObject({
id: "zxcv",
name: "Foo",
age: 20,
});
});
Now I can create a file in __mocks__/chance.js with the same signature to mock my module chance:
let chance = {};
chance.Chance = function () {
return {
guid: () => "zxcv",
name: () => "Foo",
age: () => 20,
};
};
module.exports = chance;
Now, every file you test that require/import chance, will use this mock by default.

Related

Using Jest with NodeJS, Function in imported module not being mocked without using 'this' in main

In the setup below, if I run the test as is, myFunc is not mocked when I debug into handler.
However, if instead I add this. in front of the myFunc call in handler, then the function is mocked and everything works as expected.
Can someone please explain why this is? I'm new to mocking and can't see it.
I know what this does, but why won't jest mock without it since I told it to mock that function in the module?
index.js
const aws = require('aws-sdk')
exports.handler = async function (event, context) {
let s;
switch (event.func) {
case "myFunc":
console.log('Executing myFunc');
//making the call: s = await this.myFunc.apply(null, [event.params]) will make the mock work.
s = await myFunc.apply(null, [event.params])
console.log(s);
return s;
/*cases...*/
default:
// default behaviour
}
async myFunc({p1, p2}){
/* do something */
return x
}
exports.myFunc = myFunc
}
index.spec.js
jest.mock('./index.js', () => {
const allAutoMocked = jest.createMockFromModule('./index.js')
const actual = jest.requireActual('./index.js')
return {
__esModules: true,
...allAutoMocked,
myFunc : jest.fn().mockImplementation(() => ({ mockedValue: 'test' })),
handler: actual.handler
}
})
let index = require("./index.js")
describe('Test myFunc', () => {
test('If myFunc function was called', async () => {
var event = { func: 'myFunc', params: { p1: xx, p2: false } };
const context = {};
const logMock = jest.fn((...args) => console.log(...args));
const data = await handler(event, context);
})
})

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.

Mocked copyFile and stat function not getting called in jest

I am trying to mock copyFile and stat method from fs modules(fs.promises). But the mocked function is not being called and instead the original functions are called though the test cases pass.
The testing function code is:
jest.doMock('fs', () => ({
promises: {
copyFile: (src = 'source', dest = 'destination') =>
jest.fn().mockImplementation(async () => {
console.log('Inside the mock function in copyFile, please get executed, got frustrated', src, dest);
return Promise.resolve(false);
}),
stat: () =>
jest.fn().mockImplementation(async () => {
console.log('Inside the mock function in stat method, please get executed, got frustrated');
return Promise.resolve(false); // Probably wrong datatype
}),
},
}));
describe('Testing implementation', () => {
const sample = new MainFunction()
test('Testing', async () => {
expect(sample.demo()).toEqual(Promise.resolve(true));
});
});
Actual Code which needs to be tested:
import * as fs from 'fs';
export class MainFunction {
async demo(): Promise<any> {
const fileName = 'C:/Users/Desktop/testing-file-dir/';
const fileName1 = '/destination/'
let filefound = (await fs.promises.stat(fileName)).isFile();
await fs.promises.copyFile(fileName,fileName1);
console.log(filefound, 'inside actual code');
return Promise.resolve(true);
}
}
Can someone please help regarding where I am going wrong ? I had thought of using jest.mock but it was also giving error so I followed this link https://github.com/facebook/jest/issues/2567 which suggested to try doMock. If someone knows better way to handle this mock function, it would be great.
Thanks !
You can use jest.mock(moduleName, factory, options), and you didn't mock the method chain call correctly. You should use mockFn.mockReturnThis() to return this context to the caller.
E.g.
index.ts:
import * as fs from 'fs';
export class MainFunction {
async demo(): Promise<any> {
const fileName = 'C:/Users/Desktop/testing-file-dir/';
const fileName1 = '/destination/';
let filefound = (await fs.promises.stat(fileName)).isFile();
await fs.promises.copyFile(fileName, fileName1);
console.log(filefound, 'inside actual code');
return Promise.resolve(true);
}
}
index.test.ts
import { MainFunction } from './';
jest.mock('fs', () => ({
promises: {
copyFile: jest.fn().mockImplementation((src = 'source', dest = 'destination') => {
console.log('Inside the mock function in copyFile, please get executed, got frustrated', src, dest);
return Promise.resolve(false);
}),
stat: jest.fn().mockReturnThis(),
isFile: jest.fn().mockImplementation(() => {
console.log('Inside the mock function in stat method, please get executed, got frustrated');
return Promise.resolve(false);
}),
},
}));
describe('Testing implementation', () => {
const sample = new MainFunction();
test('Testing', async () => {
const actual = await sample.demo();
expect(actual).toBeTruthy();
});
});
test result:
PASS examples/66429093/index.test.ts
Testing implementation
✓ Testing (10 ms)
console.log
Inside the mock function in stat method, please get executed, got frustrated
at Object.<anonymous> (examples/66429093/index.test.ts:12:15)
console.log
Inside the mock function in copyFile, please get executed, got frustrated C:/Users/Desktop/testing-file-dir/ /destination/
at Object.<anonymous> (examples/66429093/index.test.ts:7:15)
console.log
Promise { false } inside actual code
at MainFunction.<anonymous> (examples/66429093/index.ts:9:13)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 3.12 s, estimated 4 s
Based upon slideshowp2's solution, I had to do this change in order to avoid the error as stated in this https://github.com/facebook/jest/issues/2567.
The actual file remains same while test file changes to:
jest.mock('fs', () => {
const originalModule = jest.requireActual('fs'); // so as to not override other functions apart from below mentioned one's
return Object.assign({ __esModule: true }, originalModule, {
promises: {
copyFile: jest.fn().mockImplementation((src, dest) => {
// src, dest are parameters passed in copyFile from src to destination
let source = 'some source'; // sample source file
if (source === src) {
return true;
} else {
throw Error;
}
}),
stat: jest.fn().mockReturnThis(),
isFile: jest
.fn()
.mockImplementationOnce(() => { // I had series of test so for first one I wanted false hence this part, else we can remove this and directly use .mockImplementation()
return false;
})
.mockImplementation(() => {
return true;
}),
},
});
});
describe('Testing implementation', () => {
const sample = new MainFunction();
test('Testing', async () => {
const actual = await sample.demo();
expect(actual).toBeTruthy();
});
});

Categories