Jest: Spy on other function in module being tested - unit-testing

I'm trying to test a module using jest. In the module, I have a function which works as a kind of front to two other functions, and delegates to them based on its parameter. I'm trying to figure out if these functions have indeed been called.
I have tried using a spy function, but it is never called. Instead, the actual function is called, bypassing my spy.
All examples I have found with mocking and spies involve mocking and spying on another module than the one being tested, but in this case I am trying to spy on a function from the module being tested. Is this even possible? Is there a better way to do this?
Example code:
// funcs.js
const decide = obj => {
if (obj.a !== undefined) {
return funcA(obj);
} else {
return funcB(obj);
}
};
const funcA = obj => obj.a;
const funcB = obj => obj.b;
export { decide, funcA, funcB };
// funcs.test.js
import * as fns from "./funcs";
describe("decide", () => {
it("should delegate to 'funcA' when the parameter has an 'a' property", () => {
const spy = jest.spyOn(fns, "funcA");
fns.decide({ a: "Cake" });
expect(spy).toHaveBeenCalled(); // Error! Number of calls: 0
});
});

There's no way to spy or mock a function that is called in the same ES module it is defined.
This is possible for CommonJS modules but only if functions are consistently referred as methods:
exports.decide = obj => {
...
exports.funcA(obj);
...
};
exports.funcA = obj => obj.a;
It's possible to adopt this recipe to ES modules but this discards their benefits:
const exports = {};
export default exports;
// same as CommonJS
funcA and funcB should be either moved to separate module for testability purposes and be spied or mocked with jest.mock, or all of them should be tested as a single unit.

Related

jest.fn() v/s jest.mock()?

For mocking uuidv4, I am using this :
import { v4 as uuidv4 } from "uuid";
jest.mock("uuid");
uuidv4.mockReturnValue("uuid123");
And for mocking window.confirm, I am using this:
window.confirm = jest.fn().mockImplementation(() => true);
These both are working all right.
But when i try doing this i.e
const uuidv4 = jest.fn.mockImplementation(() => "uuid123");
I get this error
TypeError: jest.fn.mockImplementation is not a function
I am confused between jest.fn() and jest.mock().
Can someone please elaborate on which one to use and when to use, with suitable example?
Just a quick explanation for you:
jest.mock is to mock a certain module. Once you write jest.mock('uuid') then it means all exported things would be turned to a jest.Mock type, that's why you can mock v4 method: v4.mockReturnValue('yourV4Id');
jest.mock('aModule');
import {aMember} from "aModule";
// is now a jest mock type <=> jest.fn()
aMember.mockReturnValue('a value');
jest.fn is a function which returns a jest.Mock type which can be considered as an function to create whatever you want:
const aMock = jest.fn().mockReturnValue(1) // <=> const aMock = () => 1;
// The difference is jest mock type can be used to assert then. Most of cases is to check
// whether it gets called or not

How to assert a default exported function is called when mocking a module in Jest?

I'm trying to mock an external dependency in my Jest test.
My goal is to validate that the dependency was called with some specified arguments.
The dependency exports a default function, which makes it hard (impossible?) to use jest.SpyOn.
I've tried using jest.mock as follows:
const callback = jest.fn(() => 'output');
jest.mock('dependecy', () => callback);
it('call dep with some args' () => {
...
expect(callback).toBeCalledWith('arg1' , 'arg2')
});
Also tried jest.mock('dependecy', () => ({ default: callback }));.
But neither where successful, I was thinking this was because of the hoisting of jest.mock. Thus I've tried jest.doMock, but this does not even seem to call/create my mock.
Any ideas on how to validate that a method was called on an external dependency that exports a default function in jest?
Here is a simple working example that should get you going:
code.js
import dependency from 'dependency';
export const func = () => 'returned ' + dependency();
code.test.js
import { func } from './code';
import dependency from 'dependency'; // <= dependency will be...
jest.mock('dependency', () =>
jest.fn(() => 'mocked') // <= ...this mock function
);
describe('func', () => {
it('should call dependency', () => {
const result = func();
expect(result).toBe('returned mocked'); // Success!
expect(dependency).toHaveBeenCalled(); // Success!
});
});
Note that the module factory function must be self-contained so it can be hoisted by babel-jest.

use jest mock functions to test code that calls another server

Roughly, my JavaScript function that I want to unit-test looks like this:
const request = require('request-promise-native');
async function callServer() {
// Prepare parameters
// Call `request` with parameters
// Parse response JSON and return
}
Is there any way to unit-test this function without making an actual call to the real server? Can I use a jest mock function to somehow override request()? To do that, will I need to modify every function to take the request function as a parameter or there is another way?
You can mock imported module via jest.mock. https://jestjs.io/docs/en/api#requirerequiremockmodulename
describe('main description', () => {
it('description of test case', () => {
jest.mock('request-promise-native', () => {
return {}; // Return what the request-promise-native supposed to return
});
const result = callServer();
expect(result).toBe({});
});
});

How do I mock an exported typescript function in a jasmine test?

I'm trying to mock a function exported from a typescript file in a Jasmine test. I expect the following to mock the imported foo and return the value 1 in the spec for bar.
The mock appears to be uncalled, so I'm clearly missing something. How can I fix this example?
demo.ts:
export function foo(input: any): any {
return 2;
}
export function bar(input: any): any {
return foo(input) + 2;
}
demo.ts.spec:
import * as demo from './demo';
describe('foo:', () => {
it('returns 2', () => {
const actual = demo.foo(1);
expect(actual).toEqual(2);
});
});
describe('bar:', () => {
// let fooSpy;
beforeEach(() => {
spyOn(demo, 'foo' as any).and.returnValue(1); // 'as any' prevents compiler warning
});
it('verifies that foo was called', () => {
const actual = demo.bar(1);
expect(actual).toEqual(3); // mocked 1 + actual 2
expect(demo.foo).toHaveBeenCalled();
});
});
Failures:
Expected 4 to equal 3.
Expected spy foo to have been called.
Jeffery's answer helped get me on the right track.
To attach the spy is attached to the right reference for foo the product code needs to have a small change. foo() should be called as this.foo()
The below pattern works for testing (and is a lot cleaner than the convoluted work around I was using previously).
demo.ts:
export function foo(input: any): any {
return 2;
}
export function bar(input: any): any {
return this.foo(input) + 2;
}
demo.ts.spec:
import * as demo from './demo';
describe('foo:', () => {
it('returns 2', () => {
const actual = demo.foo(1);
expect(actual).toEqual(2);
});
});
describe('bar:', () => {
// let fooSpy;
beforeEach(() => {
spyOn(demo, 'foo' as any).and.returnValue(1);
});
it('verifies that foo was called', () => {
const actual = demo.bar(1);
expect(actual).toEqual(3);
expect(demo.foo).toHaveBeenCalled();
});
});
From this issue on Github: How are you expecting to use the spied on function in your actual implementation?
Your bar implementation calls the actual implementation of foo, because it has a direct reference to it. When importing in another module, a new object, with new references, is created:
// This creates a new object { foo: ..., bar: ... }
import * as demo from './demo';
These references exist only in the module of the import. When you call spyOn(demo, 'foo') it's that reference that is being used. You might want to try this in your spec, chances are the test passes:
demo.foo();
expect(demo.foo).toHaveBeenCalled();
Expecting the real implementation of bar to call a mocked foo is not really possible. Instead try to treat bar as if it had its own implementation of foo.

How to mock imported named function in Jest when module is unmocked

I have the following module I'm trying to test in Jest:
// myModule.js
export function otherFn() {
console.log('do something');
}
export function testFn() {
otherFn();
// do other things
}
As shown above, it exports some named functions and importantly testFn uses otherFn.
In Jest when I'm writing my unit test for testFn, I want to mock the otherFn function because I don't want errors in otherFn to affect my unit test for testFn. My issue is that I'm not sure the best way to do that:
// myModule.test.js
jest.unmock('myModule');
import { testFn, otherFn } from 'myModule';
describe('test category', () => {
it('tests something about testFn', () => {
// I want to mock "otherFn" here but can't reassign
// a.k.a. can't do otherFn = jest.fn()
});
});
Any help/insight is appreciated.
Use jest.requireActual() inside jest.mock()
jest.requireActual(moduleName)
Returns the actual module instead of a mock, bypassing all checks on whether the module should receive a mock implementation or not.
Example
I prefer this concise usage where you require and spread within the returned object:
// myModule.test.js
import { otherFn } from './myModule.js'
jest.mock('./myModule.js', () => ({
...(jest.requireActual('./myModule.js')),
otherFn: jest.fn()
}))
describe('test category', () => {
it('tests something about otherFn', () => {
otherFn.mockReturnValue('foo')
expect(otherFn()).toBe('foo')
})
})
This method is also referenced in Jest's Manual Mocks documentation (near the end of Examples):
To ensure that a manual mock and its real implementation stay in sync, it might be useful to require the real module using jest.requireActual(moduleName) in your manual mock and amending it with mock functions before exporting it.
Looks like I'm late to this party, but yes, this is possible.
testFn just needs to call otherFn using the module.
If testFn uses the module to call otherFn then the module export for otherFn can be mocked and testFn will call the mock.
Here is a working example:
myModule.js
import * as myModule from './myModule'; // import myModule into itself
export function otherFn() {
return 'original value';
}
export function testFn() {
const result = myModule.otherFn(); // call otherFn using the module
// do other things
return result;
}
myModule.test.js
import * as myModule from './myModule';
describe('test category', () => {
it('tests something about testFn', () => {
const mock = jest.spyOn(myModule, 'otherFn'); // spy on otherFn
mock.mockReturnValue('mocked value'); // mock the return value
expect(myModule.testFn()).toBe('mocked value'); // SUCCESS
mock.mockRestore(); // restore otherFn
});
});
import m from '../myModule';
Does not works for me, I did use:
import * as m from '../myModule';
m.otherFn = jest.fn();
I know this was asked a long time ago, but I just ran into this very situation and finally found a solution that would work. So I thought I'd share here.
For the module:
// myModule.js
export function otherFn() {
console.log('do something');
}
export function testFn() {
otherFn();
// do other things
}
You can change to the following:
// myModule.js
export const otherFn = () => {
console.log('do something');
}
export const testFn = () => {
otherFn();
// do other things
}
exporting them as a constants instead of functions. I believe the issue has to do with hoisting in JavaScript and using const prevents that behaviour.
Then in your test you can have something like the following:
import * as myModule from 'myModule';
describe('...', () => {
jest.spyOn(myModule, 'otherFn').mockReturnValue('what ever you want to return');
// or
myModule.otherFn = jest.fn(() => {
// your mock implementation
});
});
Your mocks should now work as you would normally expect.
The transpiled code will not allow babel to retrieve the binding that otherFn() is referring to. If you use a function expession, you should be able to achieve mocking otherFn().
// myModule.js
exports.otherFn = () => {
console.log('do something');
}
exports.testFn = () => {
exports.otherFn();
// do other things
}
// myModule.test.js
import m from '../myModule';
m.otherFn = jest.fn();
But as #kentcdodds mentioned in the previous comment, you probably would not want to mock otherFn(). Rather, just write a new spec for otherFn() and mock any necessary calls it is making.
So for example, if otherFn() is making an http request...
// myModule.js
exports.otherFn = () => {
http.get('http://some-api.com', (res) => {
// handle stuff
});
};
Here, you would want to mock http.get and update your assertions based on your mocked implementations.
// myModule.test.js
jest.mock('http', () => ({
get: jest.fn(() => {
console.log('test');
}),
}));
Basing on Brian Adams' answer this is how I was able to use the same approach in TypeScript. Moreover, using jest.doMock() it's possible to mock module functions only in some specific tests of a test file and provide an individual mock implementations for each of them.
src/module.ts
import * as module from './module';
function foo(): string {
return `foo${module.bar()}`;
}
function bar(): string {
return 'bar';
}
export { foo, bar };
test/module.test.ts
import { mockModulePartially } from './helpers';
import * as module from '../src/module';
const { foo } = module;
describe('test suite', () => {
beforeEach(function() {
jest.resetModules();
});
it('do not mock bar 1', async() => {
expect(foo()).toEqual('foobar');
});
it('mock bar', async() => {
mockModulePartially('../src/module', () => ({
bar: jest.fn().mockImplementation(() => 'BAR')
}));
const module = await import('../src/module');
const { foo } = module;
expect(foo()).toEqual('fooBAR');
});
it('do not mock bar 2', async() => {
expect(foo()).toEqual('foobar');
});
});
test/helpers.ts
export function mockModulePartially(
modulePath: string,
mocksCreator: (originalModule: any) => Record<string, any>
): void {
const testRelativePath = path.relative(path.dirname(expect.getState().testPath), __dirname);
const fixedModulePath = path.relative(testRelativePath, modulePath);
jest.doMock(fixedModulePath, () => {
const originalModule = jest.requireActual(fixedModulePath);
return { ...originalModule, ...mocksCreator(originalModule) };
});
}
Mocking functions of a module is moved to helper function mockModulePartially located in a separate file so it can be used from different test files (which, in common, can be located in other directories). It relies on expect.getState().testPath to fix path to a module (modulePath) being mocked (make it relative to helpers.ts containing mockModulePartially). mocksCreator function passed as a second argument to mockModulePartially should return mocks of the module. This function receives originalModule and mock implementations can optionally rely on it.
I solved my problem with a mix of the answers that I found here:
myModule.js
import * as myModule from './myModule'; // import myModule into itself
export function otherFn() {
return 'original value';
}
export function testFn() {
const result = myModule.otherFn(); // call otherFn using the module
// do other things
return result;
}
myModule.test.js
import * as myModule from './myModule';
describe('test category', () => {
let otherFnOrig;
beforeAll(() => {
otherFnOrig = myModule.otherFn;
myModule.otherFn = jest.fn();
});
afterAll(() => {
myModule.otherFn = otherFnOrig;
});
it('tests something about testFn', () => {
// using mock to make the tests
});
});
On top of the first answer here, you can use babel-plugin-rewire to mock imported named function too. You can check out the section superficially for
named function rewiring.
One of the immediate benefits for your situation here is that you do not need to change how you call the other function from your function.