Jest deep method spy - javascript

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.

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

How do you mock a named exports constructor and functions of an external library with Jest?

I have seen similar questions but nothing I have seen in the documentation or stackoverflow describes what I am trying to do. I am new to javascript and just started using jest, I have read through the jest documentation but I have not seen an example that mocks a named export of an external library. The library I am trying to mock is rate-limiter-flexible. I want to mock the named export RateLimiterRedis. I need to mock a couple of RateLimiterRedis functions, including get, consume, and delete.
For example when I mocked a function from redis all I had to do was:
import redis from 'redis';
jest.mock('redis', () => {
return { createClient: jest.fn()};
});
When I try:
jest.mock('rate-limiter-flexible', () => {
return jest.fn().mockImplementation(() => {
return { RateLimiterRedis: { get: mockGet } }
});
});
I get: TypeError: _rateLimiterFlexible.RateLimiterRedis is not a constructor
When I try:
jest.mock('rate-limiter-flexible', () => {
return { RateLimiterRedis: () => {}}
});
I get: TypeError: limiter.get is not a function
So it recognizes the constructor but I need to add the functions.
I have tried:
jest.mock('rate-limiter-flexible', () => {
return { RateLimiterRedis: () => {
return jest.fn().mockImplementation(() => {
return {
get: mockGet
}
})
},
}
});
This also gives: TypeError: limiter.get is not a function
This is in my file I am trying to test:
const limiter = new RateLimiterRedis(opts);
I have also tried doMocking the named export itself (since mock hoists itself to the top) to no success
My question boils down to how can I mock a constructor of a class and that classes functions with jest, when that class is a named export of an external library?
Edit:
mockGets definition:
const mockIpAndUrl ={
consumedPoints:1
};
const mockGet = jest.fn().mockImplementation(() => {
return mockIpAndUrl;
})
This does not work:
const mockIpAndUrl ={
consumedPoints:1
};
const mockGet = jest.fn().mockImplementation(() => {
return mockIpAndUrl;
})
jest.mock('rate-limiter-flexible', () => {
return{
RateLimiterRedis: jest.fn().mockImplementation(() => {
return { get : mockGet};
})
}
});
TypeError: limiter.get is not a function
However, this does:
jest.mock('rate-limiter-flexible', () => {
return{
RateLimiterRedis: jest.fn().mockImplementation(() => {
return { get : jest.fn().mockImplementation(() => {
return mockIpAndUrl;
})};
})
}
});
This is the documentation I was referring to:
https://jestjs.io/docs/en/es6-class-mocks#calling-jestmockdocsenjest-objectjestmockmodulename-factory-options-with-the-module-factory-parameter
This lead me to believe I could use mockGet
The export of rate-limiter-flexible is an object that is supposed to have RateLimiterRedis function that returns an instance that has get method.
"I have tried" snippet is wrong because it makes RateLimiterRedis function to return another function, and RateLimiterRedis itself isn't a spy so it cannot be asserted.
It should be:
jest.mock('rate-limiter-flexible', () => {
return {
RateLimiterRedis: jest.fn().mockImplementation(() => ({
get: mockGet
})
};
});
If RateLimiterRedis is instantiated in top-level imports, mockGet cannot be assigned before it's accessed inside a mock. It can be exported as a part of mocked module:
jest.mock('rate-limiter-flexible', () => {
const mockRateLimiterRedisGet = jest.fn();
return {
mockRateLimiterRedisGet,
RateLimiterRedis: jest.fn().mockImplementation(() => ({
get: mockRateLimiterRedisGet
})
};
});
This allows to import mockRateLimiterRedisGet from rate-limiter-flexible in a test and use for dynamically mocked values and assertions:
mockRateLimiterRedisGet.mockReturnValue(...);
...
expect(mockRateLimiterRedisGet).toBeCalled();

Mock only one function from module but leave rest with original functionality

I only want to mock a single function (named export) from a module but leave the rest of the module functions intact.
Using jest.mock('package-name') makes all exported functions mocks, which I don't want.
I tried spreading the named exports back into the mock object...
import * as utils from './utilities.js';
jest.mock(utils, () => ({
...utils
speak: jest.fn(),
}));
but got this error:
The module factory of jest.mock() is not allowed to reference any out-of-scope variables.
The highlight of this answer is jest.requireActual(), this is a very useful utility that says to jest that "Hey keep every original functionalities intact and import them".
jest.mock('./utilities.js', () => ({
...jest.requireActual('./utilities.js'),
speak: jest.fn(),
}));
Let's take another common scenario, you're using enzyme ShallowWrapper and it doesn't goes well with useContext() hook, so what're you gonna do? While i'm sure there are multiple ways, but this is the one I like:
import React from "react";
jest.mock("react", () => ({
...jest.requireActual("react"), // import and retain the original functionalities
useContext: jest.fn().mockReturnValue({foo: 'bar'}) // overwrite useContext
}))
The perk of doing it this way is that you can still use
import React, { useContext } from "react" in your original code without worrying about converting them into React.useContext() as you would if you're using jest.spyOn(React, 'useContext')
The most straightforward way is to use jest.spyOn and then .mockImplementation(). This will allow all other functions in the module to continue working how they're defined.
For packages:
import axios from 'axios';
jest.spyOn(axios, 'get');
axios.get.mockImplementation(() => { /* do thing */ });
For modules with named exports:
import * as utils from './utilities.js';
jest.spyOn(utils, 'speak');
utils.speak.mockImplementation(() => { /* do thing */ });
Docs here: https://jestjs.io/docs/en/jest-object#jestspyonobject-methodname
jest.requireActual inside of jest.mock seems like the way to go, however I needed to add a proxy instead of the object spread to prevent the type error Cannot read properties of undefined (reading ...) which can occur in certain import scenarios.
This is the final result:
jest.mock('the-module-to-mock', () => {
const actualModule = jest.requireActual('the-module-to-mock')
return new Proxy(actualModule, {
get: (target, property) => {
switch (property) {
// add cases for exports you want to mock
// 👇👇👇
case 'foo': {
return jest.fn() // add `mockImplementation` etc
}
case 'bar': {
return jest.fn()
}
// fallback to the original module
default: {
return target[property]
}
}
},
})
})
For me this worked:
const utils = require('./utilities.js');
...
jest.spyOn(utils, 'speak').mockImplementation(() => jest.fn());
I took Rico Kahler's answer and created this general purpose function:
function mockPartially(packageName: string, getMocks: (actualModule: any) => any) {
jest.doMock(packageName, () => {
const actualModule = jest.requireActual(packageName);
const mocks = getMocks(actualModule);
return new Proxy(actualModule, {
get: (target, property) => {
if (property in mocks) {
return mocks[property];
} else {
return target[property];
}
},
});
});
}
and you use it like this for example to mock lodash:
mockPartially('lodash', (_actualLodash) => { //sometimes you need the actual module
return {
'isObject': () => true, //mock isObject
'isArray': () => true // mock isArray
}
});
Manual Mocks
You can create __mocks__ directory in the same level as utilities.js and then create a file with name utilities.js inside this directory.
utilities.js
const speak = () => "Function speak";
const add = (x, y) => x + y;
const sub = (x, y) => x - y;
module.exports = { speak, add, sub };
Now, keep everything as is and just mock the speak function.
__mocks__/utilities.js
const speak = jest.fn(() => "Mocked function speak");
const add = (x, y) => x + y;
const sub = (x, y) => x - y;
module.exports = { speak, add, sub };
And now you can mock utilities.js
utilities.test.js
const { speak, add, sub } = require("./utilities");
jest.mock("./utilities");
test("speak should be mocked", () => {
expect(speak()).toBe("Mocked function speak");
});
Mocking Node Modules
Create a directory named __mocks__ in the same level as node_modules and add a file 'axios.js' inside this directory.
__mocks__/axios.js
const axios = {
get: () => Promise.resolve({ data: { name: "Mocked name" } }),
};
module.exports = axios;
fetch.js
const axios = require("axios");
const fetch = async () => {
const { data } = await axios.get(
"https://jsonplaceholder.typicode.com/users/1"
);
return data.name;
};
module.exports = fetch;
With node modules you don't need to explicitly call jest.mock("axios").
fetch.test.js
const fetch = require("./fetch");
test("axios should be mocked", async () => {
expect(await fetch()).toBe("Mocked name");
});

Unit testing chained promises

How would I unit test a class method that calls an imported class's method that is a promise? I have the following structure:
import { SomeClass } from 'some-library';
class MyClass extends AnotherClass {
myMethod() {
const someClass = new SomeClass();
return someClass.somePromiseMethod('someParam')
.then(response => response.data)
.then(response => {
// Do stuff
});
}
}
I have the following test
describe('myMethod', () => {
it('does something', async () => {
const inst = new MyClass();
const stub = sinon.stub(SomeClass, 'somePromiseMethod')
.resolves(Promise.resolve({
data: [],
}));
await inst.myMethod();
expect(stub.callCount).to.equal(1);
});
});
Which is still pretty bare as I'm not sure how to approach this. Would it be better to break down the code in the thens?
UPDATE
Apparently SomeClass is a singleton and sinon was throwing an error saying somePromiseMethod is a non-existent own property. I changed the stub to call on its prototype instead and now the stub is being called.
class MyClass extends AnotherClass {
myMethod() {
const someClassInstance = SomeClass.getInstance();
return someClassInstance.somePromiseMethod('someParam')
.then(response => response.data)
.then(response => {
// Do stuff
});
}
}
describe('myMethod', () => {
it('does something', async () => {
const inst = new MyClass();
const stub = sinon.stub(SomeClass.prototype, 'somePromiseMethod')
.resolves(Promise.resolve({
data: [],
}));
await inst.myMethod();
expect(stub.callCount).to.equal(1);
});
});
Now, since then second then would just return data, I could just put //Do stuff in a separate function and test that.
You are stubbing out the wrong method somePromiseMethod exists on the prototype of SomeClass so you need to stub that instead. Sinon should let you do something like:
const stub = sinon.stub(SomeClass.prototype, 'somePromiseMethod')
// You may be able to remove the Promise.resolve as well, as I think resolves does this for you
.resolves({
data: [],
});

Jest - import multiple tests in a describe block, reusing variables defined in beforeEach()

I am familiar with RSpec where it is very easy to reuse test cases by writing shared examples
shared_example_for 'a cute pet' do
it 'tests that the pet is a small' { expect(pet.size).to be_lesser_than(10) }
it 'tests that the pet can smile' { expect(pet.can_smile?).to be }
end
describe 'The Octocat' do
let(:pet) { Octocat.new }
it_behaves_like 'a cute pet'
end
...
describe 'The Doge' do
let(:pet) { Doge.new }
it_behaves_like 'a cute pet'
end
Is there an equivalent in Jest ? Something that would let me reuse variables set in beforeEach() blocks ? I am trying to find a way using something like the following :
# __tests__/cuteness.js
export const cutenessTests = function() {
test('it is small', () => {
expect(petSetInBefore.length).toBeLesserThan(5)
})
test('it can smile', () => {
expect(petSetInBefore.canSmile).toBe(true)
})
}
# __tests__/famous_animals.test.js
import { cutenessTests } from './cuteness'
describe('Famous animals', () => {
let petSetInBefore;
describe('Octocat', () => {
beforeEach(() => {
petSetInBefore = new Octocat();
})
cutenessTests.bind(this)()
})
})
The important here is that I am trying to share multiple test definitions and not just one, otherwise I could have passed the petSetInBefore to the shared function.
EDIT : each of my tests and nested describe are likely to alter my test environment and objects, so the beforeEach is used to restore a proper test environment. Here is a better example
class Octocat {
get strokeFor(time) {
this.strokeTime = this.strokeTime + time
if (this.strokeTime <= 10) {
this.mood = 'happy'
} else {
this.mood = 'bored'
}
}
}
class Doge {
get strokeFor(time) {
this.strokeTime = this.strokeTime + time
if (this.strokeTime <= 5) {
this.mood = 'happy'
} else {
this.mood = 'bored'
}
}
}
const cutenessTests = function() {
describe('when stroked for a short while', () => {
beforeEach(() => {
petSetInBefore.strokeFor(1);
})
test('it is happy', () => { expect(petSetInBefore.mood).to(eq('happy')) }
describe('when stroked too much', () => {
beforeEach(() => {
petSetInBefore.stroke(1000);
})
test('it gets bored', () => { expect(petSetInBefore.mood).to(eq('bored')) }
})
describe('when stroked a little longer', () => {
beforeEach(() => {
petSetInBefore.strokeFor(4);
})
test('it is still happy', () => { expect(petSetInBefore.mood).to(eq('happy')) }
})
})
}
EDIT2: Here is a repl.it based on Gui3's answer
EDIT3 : the object can be altered before or during the reusable tests
describe('Famous animals', () => {
let petSetInBefore;
describe('Octocat', () => {
beforeEach(() => {
petSetInBefore = new Octocat();
})
describe('when it is not well rested', () => {
beforeEach(() => { petSetInBefore.wellRested() } // Extra object preparation / context before calling reusable examples
cutenessTests()
}),
describe('when it is not well rested', () => {
// Calling reusable examples without extra context
cutenessTests()
})
})
})
Jest has describe.each(table) which I haven't seen being used a lot, but it's really helpful for reusing tests which have common/same results.
In case for identical expectations for both of the test subjects you can do it like this:
const aCutePet = pet => {
it("should be small", () => {
expect(pet.size).toBeLessThan(10);
});
it(`should be able to smile`, () => {
expect(pet).toHaveProperty('can_smile', true)
});
}
describe.each([
[new Doge],
[new Octocat]
])("The %O", aCutePet);
The output:
The Doge { size: 3, can_smile: true }
✓ should be small (1ms)
✓ should be able to smile (1ms)
The Octocat { size: 5, can_smile: true }
✓ should be small
✓ should be able to smile (1ms)
If you still want beforeEach,
for reasons ... it works if you declare your variable in the global scope
let petSetInBefore; // here it works
describe('Famous animals', () => {
//let petSetInBefore; // here it's undefined
describe('Octocat', () => {
//let petSetInBefore; // undefined too
beforeAll(() => {
petSetInBefore = new Octocat();
})
cutenessTests() // .bind(this) results the same
});
describe('Doge', () => {
beforeEach(() => {
petSetInBefore = new Doge();
})
cutenessTests.bind(this)()
});
})
https://repl.it/#gui3/jestSharedTests
seems like the tests inside the shared function cannot share variables from beforeEach otherwise ...
You can simply move the shared tests into a function that does the it() calls.
class Octocat {
get length() {
return 3;
}
get canSmile() {
return true;
}
}
class GrumpyCat {
get length() {
return 1;
}
get canSmile() {
return false;
}
}
const behavesLikeAPet = (pet) => {
it('is small', () => expect(pet.length).toBeLessThan(5));
it('can smile', () => expect(pet.canSmile).toEqual(true));
};
describe('Famous animals', () => {
describe('Octocat', () => {
behavesLikeAPet(new Octocat());
});
describe('GrumpyCat', () => {
behavesLikeAPet(new GrumpyCat());
});
});
You will get detailed output for every it test:
Famous animals
Octocat
✓ is small (2ms)
✓ can smile (1ms)
GrumpyCat
✓ is small
✕ can smile (2ms)

Categories