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

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

Related

Jest call default mock from a file

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.

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

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

Jest deep method spy

How can I spy on publish and publishBatch inside an instance property:
Object.defineProperty(Provider, 'instance', {
get: jest.fn(() => {
return {
publish: jest.fn(),
publishBatch: jest.fn()
}
}),
});
I'm aware of jest.spyOn(Provider, 'instance', 'get'); but I need to go deeper and couldn't find any information in the documentation.
The solution is much easier than I thought:
const obj = {
publish: jest.fn(),
publishBatch: jest.fn()
}
Object.defineProperty(Provider, 'instance', {
get: jest.fn(() => {
return obj;
}),
});
const publishSpy = jest.spyOn(obj, 'publish');
...
expect(publishSpy).toHaveBeenCalled();
A bit late on the response, but since I just looked this up myself, I wanted to share a strategy that I found to work.
# <some-dir>/your-code.js
import { SomeClass } from "./some-class";
const newClass = new SomeClass();
export function testTarget() {
const deepMethodOne = newClass.classDeepMethodOne(1);
const deepMethodTwo = deepMethodOne.classDeepMethodTwo(2);
return true;
};
# <some-dir>/your-test.js
import { testTarget } from "./your-code";
jest.mock(("./some-class") => {
class MockSomeClass {
constructor() {}
classDeepMethodOne = (...args) => mockClassDeepMethodOne(...args);
}
return {
SomeClass: MockSomeClass
}
});
describe("Testing Your Code", () => {
const mockClassDeepMethodOne = jest.fn(() => ({
classDeepMethodTwo: mockClassDeepMethodTwo
}));
const classDeepMethodTwo = jest.fn();
it("spies on mocked deep method one", () => {
testTarget();
expect(mockClassDeepMethodOne).toHaveBeenCalledWith(1);
});
it("spies on mocked deep method two", () => {
testTarget();
expect(mockClassDeepMethodTwo).toHaveBeenCalledWith(2);
})
});
The notion behind why this works is that this gets around Jest's import-level hoisting restrictions by instantiating an immediate binding to data structures that would cause a delayed effect (e.g. functions).
A little more in depth: Jest hoists your import-level mocks prior to the imports, so it does not allow you to have any immediate bindings to any unhoisted code, e.g.:
# Player 1: Go to jail, do not pass go.
class MockSomeClass {
constructor() {}
classDeepMethodOne = (...args) => mockClassDeepMethodOne(...args);
}
jest.mock(("./some-class") => ({ SomeClass: MockSomeClass }));
# Player 2: Go to jail, do not pass go.
jest.mock(("./some-class") => {
class MockSomeClass {
constructor() {}
classDeepMethodOne = mockClassDeepMethodOne;
}
return {
SomeClass: MockSomeClass
}
});
Don't ask my why, I have no idea, probably a chicken and egg problem.

Unit testing Redux async actions

I am trying to add unit test cases to my redux actions.
I have tried this, this & this
I am using thunk, promise-middleware in my actions
one of my action is like this
export function deleteCommand(id) {
return (dispatch) => {
dispatch({
type: 'DELETE_COMMAND',
payload: axios.post(`${Config.apiUrl}delete`, { _id: id })
})
}
}
unit test for this is
import configureMockStore from "redux-mock-store"
const middlewares = [thunk, promiseMiddleware()];
const mockStore = configureMockStore(middlewares)
it('creates DELETE_COMMAND_FULFILLED after deleting entry', (done) => {
nock('http://localhost:8081/')
.post('/delete',{
_id: 1
})
.reply(200, {})
const expectedActions = [{
type: 'DELETE_COMMAND_FULFILLED',
payload: {}
}]
const store = mockStore({
command: {
commands: []
}
})
store.dispatch(actions.deleteCommand({_id: 1}).then(function () {
expect(store.getActions())
.toEqual(expectedActions)
done();
})
})
I am using nock,redux-mock-store configured with thunk, promise middleware
It gives then of undefined
then I changed the action to return the promise, then I got unhandled promise reject exception, I added a catch to the action dispatch call.
Now I am getting Network Error because nock is not mocking the call.
Also tried moxios, changed axios to isomorphic-fetch, whatwg-fetch. Doesn't seem to be working
Where am I doing it wrong?.
I know It's from 2017,
But maybe someone will have the same issue.
So the problem is that the arrow function inside deleteCommand returns undefined.
That's why you get
It gives then of undefined
TLDR:
if you using redux-thunk with redux-promise-middleware
You must return the inner dispatch
here is the official redux-promise-middleware docs
The solution is very simple :
Option 1.
Add return inside the arrow function
export function deleteCommand(id) {
return (dispatch) => {
return dispatch({
type: 'DELETE_COMMAND',
payload: axios.post(`${Config.apiUrl}delete`, { _id: id })
})
}
}
Option 2.
Remove the curly braces from the arrow function
export function deleteCommand(id) {
return (dispatch) =>
dispatch({
type: 'DELETE_COMMAND',
payload: axios.post(`${Config.apiUrl}delete`, { _id: id })
})
}
Now you can do
store.deleteCommand(<some id>).then(...)
In general arrow functions and return
Return a plain object
// option 1
const arrowFuncOne = () => {
return {...someProps}
}
// option 2
const arrowFuncTwo = () => ({
...someProps
})
// This means that you returning an expression
//same as
// return a result of other function
const arrowFuncCallFunOne = () => callSomeOtherFunc()
// or
const arrowCallFunkTwo = () => {return callSomeOtherFunc()}
// However
// wrong
const arrowCallFunkNoReturn = () => {callSomeOtherFunc()}
// in this case the curly braces are just the body of the function
// and inside this body there is no return at all

Categories