Related
I am having an issue with mocking functions called from within my module. when the mocked function is called from the test, it works as expected. but when that same mocked function is called from the library, it calls the actual function.
I have tried the jest.mock('./myModule', () => {}) approach, and enableAutomock as well, but with the same results.
I feel like i have not had this problem in other projects, but have looked through my jest configuration, and don't see anything that would effect it.
what am i missing here? how can i mock functions called internally within my module?
// myModule.js
export function foo() {
return 'foo'
}
export function bar() {
return foo()
}
// myModule.test.js
import * as myModule from './myModule';
jest.spyOn(myModule, 'foo').mockReturnValue('mock foo');
// i have also tried...
// jest.mock('./myModule', () => {
// ...(jest.requireActual('./myModule')),
// foo: jest.fn().mockReturnValue('mock foo')
// });
it('should', () => {
expect(myModule.foo()).toEqual('mock foo'); // PASS: returns 'mock foo'
expect(myModule.bar()).toEqual('mock foo'); // FAIL: returns 'foo'
});
I found using ts-jest in my jest config, allowed these tests to run as i expected
transform: {
'^.+\\.[tj]sx?$': ['ts-jest'],
},
What's the best way to correctly mock the following example?
The problem is that after import time, foo keeps the reference to the original unmocked bar.
module.js:
export function bar () {
return 'bar';
}
export function foo () {
return `I am foo. bar is ${bar()}`;
}
module.test.js:
import * as module from '../src/module';
describe('module', () => {
let barSpy;
beforeEach(() => {
barSpy = jest.spyOn(
module,
'bar'
).mockImplementation(jest.fn());
});
afterEach(() => {
barSpy.mockRestore();
});
it('foo', () => {
console.log(jest.isMockFunction(module.bar)); // outputs true
module.bar.mockReturnValue('fake bar');
console.log(module.bar()); // outputs 'fake bar';
expect(module.foo()).toEqual('I am foo. bar is fake bar');
/**
* does not work! we get the following:
*
* Expected value to equal:
* "I am foo. bar is fake bar"
* Received:
* "I am foo. bar is bar"
*/
});
});
I could change:
export function foo () {
return `I am foo. bar is ${bar()}`;
}
to:
export function foo () {
return `I am foo. bar is ${exports.bar()}`;
}
but this is pretty ugly in my opinion to do everywhere.
An alternative solution can be importing the module into its own code file and using the imported instance of all of the exported entities. Like this:
import * as thisModule from './module';
export function bar () {
return 'bar';
}
export function foo () {
return `I am foo. bar is ${thisModule.bar()}`;
}
Now mocking bar is really easy, because foo is also using the exported instance of bar:
import * as module from '../src/module';
describe('module', () => {
it('foo', () => {
spyOn(module, 'bar').and.returnValue('fake bar');
expect(module.foo()).toEqual('I am foo. bar is fake bar');
});
});
Importing the module into its own code looks strange, but due to the ES6's support for cyclic imports, it works really smoothly.
The problem seems to be related to how you expect the scope of bar to be resolved.
On one hand, in module.js you export two functions (instead of an object holding these two functions). Because of the way modules are exported the reference to the container of the exported things is exports like you mentioned it.
On the other hand, you handle your export (that you aliased module) like an object holding these functions and trying to replace one of its function (the function bar).
If you look closely at your foo implementation you are actually holding a fixed reference to the bar function.
When you think you replaced the bar function with a new one you just actually replaced the reference copy in the scope of your module.test.js
To make foo actually use another version of bar you have two possibilities :
In module.js export a class or an instance, holding both the foo and bar method:
Module.js:
export class MyModule {
function bar () {
return 'bar';
}
function foo () {
return `I am foo. bar is ${this.bar()}`;
}
}
Note the use of this keyword in the foo method.
Module.test.js:
import { MyModule } from '../src/module'
describe('MyModule', () => {
//System under test :
const sut:MyModule = new MyModule();
let barSpy;
beforeEach(() => {
barSpy = jest.spyOn(
sut,
'bar'
).mockImplementation(jest.fn());
});
afterEach(() => {
barSpy.mockRestore();
});
it('foo', () => {
sut.bar.mockReturnValue('fake bar');
expect(sut.foo()).toEqual('I am foo. bar is fake bar');
});
});
Like you said, rewrite the global reference in the global exports container. This is not a recommended way to go as you will possibly introduce weird behaviors in other tests if you don't properly reset the exports to its initial state.
fwiw, the solution I settled on was to use dependency injection, by setting a default argument.
So I would change
export function bar () {
return 'bar';
}
export function foo () {
return `I am foo. bar is ${bar()}`;
}
to
export function bar () {
return 'bar';
}
export function foo (_bar = bar) {
return `I am foo. bar is ${_bar()}`;
}
This is not a breaking change to the API of my component, and I can easily override bar in my test by doing the following
import { foo, bar } from '../src/module';
describe('module', () => {
it('foo', () => {
const dummyBar = jest.fn().mockReturnValue('fake bar');
expect(foo(dummyBar)).toEqual('I am foo. bar is fake bar');
});
});
This has the benefit of leading to slightly nicer test code too :)
I had this same problem and due to the project's linting standards, defining a class or rewriting references in the exports were not code review approvable options even if not prevented by the linting definitions. What I stumbled on as a viable option is to use the babel-rewire-plugin which is much cleaner, at least in appearance. While I found this used in another project I had access to, I noticed it was already in an answer in a similar question which I have linked here. This is a snippet adjusted for this question (and without using spies) provided from the linked answer for reference (I also added semicolons in addition to removing spies because I'm not a heathen):
import __RewireAPI__, * as module from '../module';
describe('foo', () => {
it('calls bar', () => {
const barMock = jest.fn();
__RewireAPI__.__Rewire__('bar', barMock);
module.foo();
expect(bar).toHaveBeenCalledTimes(1);
});
});
https://stackoverflow.com/a/45645229/6867420
Works for me:
cat moduleWithFunc.ts
export function funcA() {
return export.funcB();
}
export function funcB() {
return false;
}
cat moduleWithFunc.test.ts
import * as module from './moduleWithFunc';
describe('testFunc', () => {
beforeEach(() => {
jest.clearAllMocks();
});
afterEach(() => {
module.funcB.mockRestore();
});
it.only('testCase', () => {
// arrange
jest.spyOn(module, 'funcB').mockImplementationOnce(jest.fn().mockReturnValue(true));
// act
const result = module.funcA();
// assert
expect(result).toEqual(true);
expect(module.funcB).toHaveBeenCalledTimes(1);
});
});
From this thread:
Try using a function expression
export const bar = () => {
return "bar"
}
This should let you spy on bar even if its used by another function in the same module.
If you define your exports you can then reference your functions as part of the exports object. Then you can overwrite the functions in your mocks individually. This is due to how the import works as a reference, not a copy.
module.js:
exports.bar () => {
return 'bar';
}
exports.foo () => {
return `I am foo. bar is ${exports.bar()}`;
}
module.test.js:
describe('MyModule', () => {
it('foo', () => {
let module = require('./module')
module.bar = jest.fn(()=>{return 'fake bar'})
expect(module.foo()).toEqual('I am foo. bar is fake bar');
});
})
If you're using Babel (i.e. #babel/parser) to handle transpiling your code, the babel-plugin-explicit-exports-references1 npm package solves this pretty elegantly by making the "ugly" module.exports replacements for you transparently at transpile time. See the original problem thread for more information.
1 Note: I wrote this plugin!
For CommonJS modules users, suppose the file looks something like:
/* myModule.js */
function bar() {
return "bar";
}
function foo() {
return `I am foo. bar is ${bar()}`;
}
module.exports = { bar, foo };
You need to modify the file to:
/* myModule.js */
function bar() {
return "bar";
}
function foo() {
return `I am foo. bar is ${myModule.bar()}`; // Change `bar()` to `myModule.bar()`
}
const myModule = { bar, foo }; // Items you wish to export
module.exports = myModule; // Export the object
Your original test suite (myModule.test.js) should now pass:
const myModule = require("./myModule");
describe("myModule", () => {
test("foo", () => {
jest.spyOn(myModule, "bar").mockReturnValueOnce("bar-mock");
const result = myModule.foo();
expect(result).toBe("I am foo. bar is bar-mock");
});
});
Read more: Mock/Spy exported functions within a single module in Jest
There are various hacks available here to make this work, but the real answer most people should be using is: don't. Taking the OP's example module:
export function bar () {
return 'bar';
}
export function foo () {
return `I am foo. bar is ${bar()}`;
}
and testing the actual behaviour, you'd write:
import { bar, foo } from "path/to/module";
describe("module", () => {
it("foo returns 'bar'", () => {
expect(bar()).toBe('bar');
});
it("foo returns 'I am foo. bar is bar'", () => {
expect(foo()).toBe('I am foo. bar is bar');
});
});
Why? Because then you can refactor inside the module boundary without changing the tests, which gives you the confidence to improve the quality of your code in the knowledge that it still does what it's supposed to.
Imagine you extracted the creation of 'bar' from bar to an unexported function, for example:
function rawBar() {
return 'bar';
}
export function bar () {
return rawBar();
}
export function foo () {
return `I am foo. bar is ${rawBar()}`;
}
The test I suggest above would pass. If you'd asserted that calling foo meant bar got called, that test would start failing, even though the refactor preserved the module's behaviour (same API, same outputs). That's an implementation detail.
Test doubles are for collaborators, if something really does need to be mocked here it should be extracted to a separate module (then mocking it is much easier, which tells you you're moving in the right direction). Trying to mock functions in the same module is like mocking parts of a class you're trying to test, which I illustrate similarly here: https://stackoverflow.com/a/66752334/3001761.
I have 2 javascript A and B functions defined as below and are exposed via another function called API as shown below, I want to test function A to see if it is calling B or not.
function A () {
B()
}
function B () {
console.log('B is called')
}
export function API (){
return {
a: A,
b: B
}
}
The way i have tried my test functions are as below which do not work.
import { API } from './actions-beta'
describe('test A', () => {
test('', () => {
const fn = API()
console.log(fn)
const spy = jest.spyOn(fn, 'b')
fn.a()
expect(spy).toHaveBeenCalled()
})
})
There is no way to test if B is called the way the code is written.
Details
jest.spyOn replaces the function property on the object with a spy.
jest.spyOn(fn, 'b') will replace the b property on the object fn with a spy.
fn.a() calls A which calls B directly, it does not call fn.b so the spy is never called.
Solution
A needs to call B using an object property that can be replaced with a spy during the test.
When creating a spy, the object is typically the module.
This is why spying on exported functions is quite easy and spying on non-exported functions is quite difficult, which brings up an important point: if a function calls a non-exported function within the same module then it is just an implementation detail that is not visible outside of the module and with a black box testing approach it does not need to be tested.
If you find that B is more than an implementation detail and you want to spy or stub its functionality then the easiest approach (especially for this code where the export is a function that creates a new object every time it is invoked) is to move B into its own module:
actions-beta.js
import { B } from './lib';
export function A () {
B()
}
export function API (){
return {
a: A,
b: B
}
}
lib.js
export function B () {
console.log('B is called')
}
The test:
import { API } from './actions-beta'
import * as lib from './lib'; // import the module with B
describe('test A', () => {
test('', () => {
const fn = API()
console.log(fn)
const spy = jest.spyOn(lib, 'B') // spy on B using its module
fn.a()
expect(spy).toHaveBeenCalled() // SUCCESS
})
})
What's the best way to correctly mock the following example?
The problem is that after import time, foo keeps the reference to the original unmocked bar.
module.js:
export function bar () {
return 'bar';
}
export function foo () {
return `I am foo. bar is ${bar()}`;
}
module.test.js:
import * as module from '../src/module';
describe('module', () => {
let barSpy;
beforeEach(() => {
barSpy = jest.spyOn(
module,
'bar'
).mockImplementation(jest.fn());
});
afterEach(() => {
barSpy.mockRestore();
});
it('foo', () => {
console.log(jest.isMockFunction(module.bar)); // outputs true
module.bar.mockReturnValue('fake bar');
console.log(module.bar()); // outputs 'fake bar';
expect(module.foo()).toEqual('I am foo. bar is fake bar');
/**
* does not work! we get the following:
*
* Expected value to equal:
* "I am foo. bar is fake bar"
* Received:
* "I am foo. bar is bar"
*/
});
});
I could change:
export function foo () {
return `I am foo. bar is ${bar()}`;
}
to:
export function foo () {
return `I am foo. bar is ${exports.bar()}`;
}
but this is pretty ugly in my opinion to do everywhere.
An alternative solution can be importing the module into its own code file and using the imported instance of all of the exported entities. Like this:
import * as thisModule from './module';
export function bar () {
return 'bar';
}
export function foo () {
return `I am foo. bar is ${thisModule.bar()}`;
}
Now mocking bar is really easy, because foo is also using the exported instance of bar:
import * as module from '../src/module';
describe('module', () => {
it('foo', () => {
spyOn(module, 'bar').and.returnValue('fake bar');
expect(module.foo()).toEqual('I am foo. bar is fake bar');
});
});
Importing the module into its own code looks strange, but due to the ES6's support for cyclic imports, it works really smoothly.
The problem seems to be related to how you expect the scope of bar to be resolved.
On one hand, in module.js you export two functions (instead of an object holding these two functions). Because of the way modules are exported the reference to the container of the exported things is exports like you mentioned it.
On the other hand, you handle your export (that you aliased module) like an object holding these functions and trying to replace one of its function (the function bar).
If you look closely at your foo implementation you are actually holding a fixed reference to the bar function.
When you think you replaced the bar function with a new one you just actually replaced the reference copy in the scope of your module.test.js
To make foo actually use another version of bar you have two possibilities :
In module.js export a class or an instance, holding both the foo and bar method:
Module.js:
export class MyModule {
function bar () {
return 'bar';
}
function foo () {
return `I am foo. bar is ${this.bar()}`;
}
}
Note the use of this keyword in the foo method.
Module.test.js:
import { MyModule } from '../src/module'
describe('MyModule', () => {
//System under test :
const sut:MyModule = new MyModule();
let barSpy;
beforeEach(() => {
barSpy = jest.spyOn(
sut,
'bar'
).mockImplementation(jest.fn());
});
afterEach(() => {
barSpy.mockRestore();
});
it('foo', () => {
sut.bar.mockReturnValue('fake bar');
expect(sut.foo()).toEqual('I am foo. bar is fake bar');
});
});
Like you said, rewrite the global reference in the global exports container. This is not a recommended way to go as you will possibly introduce weird behaviors in other tests if you don't properly reset the exports to its initial state.
fwiw, the solution I settled on was to use dependency injection, by setting a default argument.
So I would change
export function bar () {
return 'bar';
}
export function foo () {
return `I am foo. bar is ${bar()}`;
}
to
export function bar () {
return 'bar';
}
export function foo (_bar = bar) {
return `I am foo. bar is ${_bar()}`;
}
This is not a breaking change to the API of my component, and I can easily override bar in my test by doing the following
import { foo, bar } from '../src/module';
describe('module', () => {
it('foo', () => {
const dummyBar = jest.fn().mockReturnValue('fake bar');
expect(foo(dummyBar)).toEqual('I am foo. bar is fake bar');
});
});
This has the benefit of leading to slightly nicer test code too :)
I had this same problem and due to the project's linting standards, defining a class or rewriting references in the exports were not code review approvable options even if not prevented by the linting definitions. What I stumbled on as a viable option is to use the babel-rewire-plugin which is much cleaner, at least in appearance. While I found this used in another project I had access to, I noticed it was already in an answer in a similar question which I have linked here. This is a snippet adjusted for this question (and without using spies) provided from the linked answer for reference (I also added semicolons in addition to removing spies because I'm not a heathen):
import __RewireAPI__, * as module from '../module';
describe('foo', () => {
it('calls bar', () => {
const barMock = jest.fn();
__RewireAPI__.__Rewire__('bar', barMock);
module.foo();
expect(bar).toHaveBeenCalledTimes(1);
});
});
https://stackoverflow.com/a/45645229/6867420
Works for me:
cat moduleWithFunc.ts
export function funcA() {
return export.funcB();
}
export function funcB() {
return false;
}
cat moduleWithFunc.test.ts
import * as module from './moduleWithFunc';
describe('testFunc', () => {
beforeEach(() => {
jest.clearAllMocks();
});
afterEach(() => {
module.funcB.mockRestore();
});
it.only('testCase', () => {
// arrange
jest.spyOn(module, 'funcB').mockImplementationOnce(jest.fn().mockReturnValue(true));
// act
const result = module.funcA();
// assert
expect(result).toEqual(true);
expect(module.funcB).toHaveBeenCalledTimes(1);
});
});
From this thread:
Try using a function expression
export const bar = () => {
return "bar"
}
This should let you spy on bar even if its used by another function in the same module.
If you define your exports you can then reference your functions as part of the exports object. Then you can overwrite the functions in your mocks individually. This is due to how the import works as a reference, not a copy.
module.js:
exports.bar () => {
return 'bar';
}
exports.foo () => {
return `I am foo. bar is ${exports.bar()}`;
}
module.test.js:
describe('MyModule', () => {
it('foo', () => {
let module = require('./module')
module.bar = jest.fn(()=>{return 'fake bar'})
expect(module.foo()).toEqual('I am foo. bar is fake bar');
});
})
If you're using Babel (i.e. #babel/parser) to handle transpiling your code, the babel-plugin-explicit-exports-references1 npm package solves this pretty elegantly by making the "ugly" module.exports replacements for you transparently at transpile time. See the original problem thread for more information.
1 Note: I wrote this plugin!
For CommonJS modules users, suppose the file looks something like:
/* myModule.js */
function bar() {
return "bar";
}
function foo() {
return `I am foo. bar is ${bar()}`;
}
module.exports = { bar, foo };
You need to modify the file to:
/* myModule.js */
function bar() {
return "bar";
}
function foo() {
return `I am foo. bar is ${myModule.bar()}`; // Change `bar()` to `myModule.bar()`
}
const myModule = { bar, foo }; // Items you wish to export
module.exports = myModule; // Export the object
Your original test suite (myModule.test.js) should now pass:
const myModule = require("./myModule");
describe("myModule", () => {
test("foo", () => {
jest.spyOn(myModule, "bar").mockReturnValueOnce("bar-mock");
const result = myModule.foo();
expect(result).toBe("I am foo. bar is bar-mock");
});
});
Read more: Mock/Spy exported functions within a single module in Jest
There are various hacks available here to make this work, but the real answer most people should be using is: don't. Taking the OP's example module:
export function bar () {
return 'bar';
}
export function foo () {
return `I am foo. bar is ${bar()}`;
}
and testing the actual behaviour, you'd write:
import { bar, foo } from "path/to/module";
describe("module", () => {
it("foo returns 'bar'", () => {
expect(bar()).toBe('bar');
});
it("foo returns 'I am foo. bar is bar'", () => {
expect(foo()).toBe('I am foo. bar is bar');
});
});
Why? Because then you can refactor inside the module boundary without changing the tests, which gives you the confidence to improve the quality of your code in the knowledge that it still does what it's supposed to.
Imagine you extracted the creation of 'bar' from bar to an unexported function, for example:
function rawBar() {
return 'bar';
}
export function bar () {
return rawBar();
}
export function foo () {
return `I am foo. bar is ${rawBar()}`;
}
The test I suggest above would pass. If you'd asserted that calling foo meant bar got called, that test would start failing, even though the refactor preserved the module's behaviour (same API, same outputs). That's an implementation detail.
Test doubles are for collaborators, if something really does need to be mocked here it should be extracted to a separate module (then mocking it is much easier, which tells you you're moving in the right direction). Trying to mock functions in the same module is like mocking parts of a class you're trying to test, which I illustrate similarly here: https://stackoverflow.com/a/66752334/3001761.
in es6 there you can define a module of functions like this
export default {
foo() { console.log('foo') },
bar() { console.log('bar') },
baz() { foo(); bar() }
}
the above seems to be valid code, but if I call baz() it throws an error:
ReferenceError: foo is not defined
How do you call foo from another function? in this case baz
Edit
Here's the code that actually doesn't work. I have simplified the code so it's only the core as needed
const tokenManager = {
revokeToken(headers) {
...
},
expireToken(headers) {
...
},
verifyToken(req, res, next) {
jwt.verify(... => {
if (err) {
expireToken(req.headers)
}
})
}
}
export default tokenManager
and the error is
expireToken(req.headers);
^
ReferenceError: expireToken is not defined
Edit 2
I just tried adding tokenManager before expireToken and it finally works
The export default {...} construction is just a shortcut for something like this:
const funcs = {
foo() { console.log('foo') },
bar() { console.log('bar') },
baz() { foo(); bar() }
}
export default funcs
It must become obvious now that there are no foo, bar or baz functions in the module's scope. But there is an object named funcs (though in reality it has no name) that contains these functions as its properties and which will become the module's default export.
So, to fix your code, re-write it without using the shortcut and refer to foo and bar as properties of funcs:
const funcs = {
foo() { console.log('foo') },
bar() { console.log('bar') },
baz() { funcs.foo(); funcs.bar() } // here is the fix
}
export default funcs
Another option is to use this keyword to refer to funcs object without having to declare it explicitly, as #pawel has pointed out.
Yet another option (and the one which I generally prefer) is to declare these functions in the module scope. This allows to refer to them directly:
function foo() { console.log('foo') }
function bar() { console.log('bar') }
function baz() { foo(); bar() }
export default {foo, bar, baz}
And if you want the convenience of default export and ability to import items individually, you can also export all functions individually:
// util.js
export function foo() { console.log('foo') }
export function bar() { console.log('bar') }
export function baz() { foo(); bar() }
export default {foo, bar, baz}
// a.js, using default export
import util from './util'
util.foo()
// b.js, using named exports
import {bar} from './util'
bar()
Or, as #loganfsmyth suggested, you can do without default export and just use import * as util from './util' to get all named exports in one object.
One alternative is to change up your module. Generally if you are exporting an object with a bunch of functions on it, it's easier to export a bunch of named functions, e.g.
export function foo() { console.log('foo') },
export function bar() { console.log('bar') },
export function baz() { foo(); bar() }
In this case you are export all of the functions with names, so you could do
import * as fns from './foo';
to get an object with properties for each function instead of the import you'd use for your first example:
import fns from './foo';
tl;dr: baz() { this.foo(); this.bar() }
In ES2015 this construct:
var obj = {
foo() { console.log('foo') }
}
is equal to this ES5 code:
var obj = {
foo : function foo() { console.log('foo') }
}
exports.default = {} is like creating an object, your default export translates to ES5 code like this:
exports['default'] = {
foo: function foo() {
console.log('foo');
},
bar: function bar() {
console.log('bar');
},
baz: function baz() {
foo();bar();
}
};
now it's kind of obvious (I hope) that baz tries to call foo and bar defined somewhere in the outer scope, which are undefined. But this.foo and this.bar will resolve to the keys defined in exports['default'] object. So the default export referencing its own methods shold look like this:
export default {
foo() { console.log('foo') },
bar() { console.log('bar') },
baz() { this.foo(); this.bar() }
}
See babel repl transpiled code.