How to mock a nested function with Jest - javascript

I've tried a couple of approaches but I'm not sure if it's possible to mock a nested function in Jest. I read the official docs where they use axios, but I'm trying to mock a function that is imported, not a module.
for example, I have some function A, and within A I call B. I want to mock B so that it returns true once and then false once.
B is a function I imported that I wrote in another file, and I'm testing A.
so far I have tried to import function B into my test file and create a mock of it, but I don't think it's working how I thought it would
for example I have tried this
import { b } from "../util"
jest.mock("b", () => jest.fn().mockReturnValue(true));
and then my tested file would be
function a() => {
while b() {
do something;
}
}
should I rewrite my test or function I'm testing?

Not sure what kind of results you are getting, but for mocking b you could do:
jest.mock("../util", () => ({
b:jest.fn().mockReturnValueOnce(true).mockReturnValueOnce(false),
}));

Related

How to spyOn a function from the same module in jest? [duplicate]

This question already has answers here:
How to mock functions in the same module using Jest?
(10 answers)
Closed 6 months ago.
Consider this set of code snippets where I want to write unit tests for function A, which internally calls function B during execution. Assume B is a set of API calls for validation that I want to mock return value to true.
But this spyOn method approach does not work, and the API calls in B still get executed. I've seen approaches with mocking the complete module with jest.requireActual(), but they do not seem to work too.
What could be a better way to test such functions without changing a lot in the codebase index.ts?
//index.ts
async function A (a:string, b:string, c:string) {
// code
await B(data);
// code
}
async function B (data:customType) {
//code
//API calls
//code
}
export default {
A,
B }
//index.test.ts
import index from '../index.ts';
describe('Test suit', ()=>{
it('should be a test for function A', async ()=> {
jest.spyOn(index, 'B').mockReturnValue(true);
// code
const result = await index.A('a','b','c');
// code
})
})
There is not really a good way to do it, when the functions are in the same module with jest only.
But you can with Rewire, it's explained in this answer and worked very well in a similar situation for me: https://stackoverflow.com/a/52725067/16068019
Otherwise if it's an option you could move one of your functions to a different module, but that's not always an option.

global Jest SpyOn function doesen't call the original function

I hope someone might help me understanding the interactivity of js prototypes and jest.spOn().
I have a small Example:
An example class in file TestObj.ts:
export default class TestObj {
foo() {
// Do Something e.g.
console.log("Hello World!");
}
}
The following example Test Case is succeeding but the console.log is never executed.
import TestObj from './TestObj';
const spyObj = jest.spyOn(TestObj.prototype, 'foo');
test('debug test', () => {
const obj = new TestObj();
obj.foo();
expect(spyObj).toHaveBeenCalled();
});
If I change the Example Test Case to the following, the test succeeds and the console.log statement is called as expected.
import TestObj from './TestObj';
test('debug test', () => {
const spyObj = jest.spyOn(TestObj.prototype, 'foo');
const obj = new TestObj();
obj.foo();
expect(spyObj).toHaveBeenCalled();
});
Any idea why the version, using the global spyOn variable does not work as expected?
Edit:
It seems to be not related to prototypes.
The same Issue is there for a function without any kind of class,
editing the First Code snippet (TestObj.ts) to this:
export foo() {
// Do Something e.g.
console.log("Hello World!");
};
We receve the same issue for the updated second snipped. (The test succeeds but the console log is never reached.)
import * as testlib from './TestObj';
const spyObj = jest.spyOn(testlib, 'foo');
test('debug test', () => {
testlib.foo();
expect(spyObj).toHaveBeenCalled();
});
However if we update the second snippet to the following the test succeeds and the console log is executed:
import * as testlib from './TestObj';
const spyObj: jest.SpyInstance;
beforeEach(() => {
spyObj = jest.spyOn(testlib, 'foo');
});
test('debug test', () => {
testlib.foo();
expect(spyObj).toHaveBeenCalled();
});
However I have still no clue why I discover this issue.
who ever comes across this post,
Problem explanation
I did a lot more of research(try&error, mdn, jest manpage and a lot of medium articles) and I guess that I found out the reason for the strange behavior. To understand this issue it is important to know a number of points:
1: JS prototypes are global variables, every object of the corresponding type relies on.
2: Jest does not reset global variables after every test, changes made before, or inside any test to a global variable will be kept for the whole test suite (file).
3: The jest spy on function is actually a mockup of the specified function with an implementation calling the function it self. e.g.:
jest.SpyOn(TestObj.prototype, 'foo'); actually is implemented as: TestObj.prototype.foo = new jest.fn().mockImplementation(()=>{original_TestObj.prototype.foo()});
This means spying on a function of a class prototype is actually changing a global variable.
4: Depending on your jest config, there is the possibility to reset mockup functions before every test to the default value. But be careful the default function for spyOn seems to be the same as for jest.fn() it self, an empty implementation, which means the mock-up is still callable but no code, especially not the original implementation is executed.
Solution
avoid changing global variables, if you want your testcases independent from each other.
avoid spying on prototypes, in test cases, if you require the spy only in a single test try to spy on the local object eg:
test('should foo', () => {
const testObj = new TestObj();
const spyOnFn = jest.spyOn(testObj, 'foo');
// Do anything
expect(spyOnFn).to//Have been anything
});
if requiring spyOn implementations of the same function
in more than one test try to create a global variable for the Test
but use the before each functionality of jest to setup the Spy. This
functionality is executed after the reset of all mocks (if enabled).
e.g.:
let spyOnFunction1: jest.SpyInstance;
beforeEach(()=> {
spyOnFunction1 = jest.spyOn(TestObj.prototype, 'foo');
});

Jest spyOn not works

So I have a file that consists of a couple of functions. And I wrote a test for getOpeningHours.
The getOpeningHours use isLocationOrderAheadAvailable function
The isLocationOrderAheadAvailable function is not using getOpeningHours so it's not a circular dep issue
Both getOpeningHours and isLocationOrderAheadAvailable are exported functions
Both functions came from same file.
import * as locationUtils from './location';
import {
isLocationOrderAheadAvailable,
getProductItemsFromMenu,
resolveOpeningHours,
getOpenedTimeRanges,
getOpeningHours,
} from './location';
describe('getOpeningHours', () => {
it('should return same locationHours if isLocationOrderAheadAvailable is false', () => {
jest.spyOn(locationUtils, 'isLocationOrderAheadAvailable').mockImplementation(
jest.fn().mockReturnValue(false)
);
const openingHours = getOpeningHours(validLocationEntry);
expect(openingHours).toEqual(locationHours);
});
});
So eventually the isLocationOrderAheadAvailable not been mocked at all. The wired thing is that I did exactly the same in a different test and it worked.
Sorry, I can't give a working example of this.
Changing from export function isLocationOrderAheadAvailable(...) to export const isLocationOrderAheadAvailable = (...): => makes jest.spyOn work.
But I still don't understand why. I think it might be connected with JS hoisting but I don't know how jest interacts with it.

Using babel-plugin-rewire to test private non-referenced functions

I am using babel-plugin-rewire (https://www.npmjs.com/package/babel-plugin-rewire) in main.test.js to test non-exported functions in main.js. This has been working except in the case where the function is not referenced in main.js; in this case I get the following error: TypeError: _get__(...) is not a function.
Only after I add a reference to the function in main.js I am able to access it in the test file (it works even if I don't actually call the function). However I do not want to make any changes to main.js. Is this the expected behavior of babel-plugin-rewire, and is there a workaround for this?
//main.js
function test123() {
return true;
}
test123; //Cannot access function in test file unless I add this reference!
//main.test.js
const test123 = require('./main').__get__('test123');
test('test123', () => {
expect(test123()).toEqual(true);
});
You can test non-exported functions in main.js, but the non-exported functions need to be used in at least one exported function in the same file.
In your case, this will work without a reference.
//main.js
export default function foo() {
test123();
}
function test123() {
return true;
}

How to sinon spy module export utility functions

In javascript (ES6), I have a utility module which just contains some functions, and then in the end of the file, I export them like so:
module.exports = {
someFunction1,
someFunction2,
someFunction3,
}
Then I want to write unit tests for those functions. Some of the functions depend on each other; they call each other in a way, that for example, someFunction1 might call someFunction2. There's no circular problems.
Everything works well, until I need to spy that one of the functions is called. How can I do it? Currently I'm using Chai and Sinon.
In the test file, I have imported the whole file as a module:
const wholeModule = require('path/to/the/js/file')
And finally, my test looks like following:
it('should call the someFunction2', (done) => {
const spy = sinon.spy(wholeModule, 'someFunction2')
wholeModule.someFunction1() // someFunction2 is called inside someFunction1
assert(spy.calledOnce, 'someFunction2 should be called once')
done()
})
The problem is, that the test fails, because in someFunction1, the someFunction2 function is used directly. I apply the spy to the module object's function. But that's a different object. Here's an example of the someFunction1:
function someFunction1() {
someFunction2()
return 2;
}
I know the reason why it won't work, but I don't know what would be the best practise in this case to make it work? Please help!
You can use rewire module. Here is an example:
Source code:
function someFunction1() {
console.log('someFunction1 called')
someFunction2();
}
function someFunction2() {
console.log('someFunction2 called')
}
module.exports = {
someFunction1: someFunction1,
someFunction2: someFunction2
}
Test case:
'use strict';
var expect = require('chai').expect;
var rewire = require('rewire');
var sinon = require('sinon');
var funcs = rewire('../lib/someFunctions');
it('should call the someFunction2', () => {
var someFunction2Stub = sinon.stub();
funcs.__set__({
someFunction2: someFunction2Stub,
});
someFunction2Stub.returns(null);
funcs.someFunction1();
expect(someFunction2Stub.calledOnce).to.equal(true);
});
As you are already aware this happens because you are stubbing the exported reference not the actual method in the module. It works in any module that includes the one you are replacing since they include the exported references but when inside the same module it's just calling the local function.
The easiest solution I've found is just to call the reference:
function someFunction1() {
this.someFunction2()
return 2;
}

Categories

Resources