Reputation: 1715
The ES6 module that I want to test looks as follows:
function privateFunction() {
...
}
export function publicFunction() {
... does something ...
privateFunction()
... does something else ...
}
I am using JEST for my unit tests and I am trying to find a way to test publicFunction
and avoiding the execution of privateFunction
by mocking it but I couldn't succeed in the mock attempt. Any idea?
Upvotes: 59
Views: 90762
Reputation: 571
If you want to mock a private function, try to use the prototype
.
For example, you need to mock privateFunction
of the following class:
export class Module {
public publicFunction() {
// do something
this.privateFunction();
// do something
}
private privateFunction() {
// do something
}
}
So you should use Module.prototype
in the jest.spyOn
function.
import { Module } from './my-module';
describe('MyModule', () => {
it('tests public function', () => {
// Arrange
const module = new Module()
const myPrivateFunc = jest.spyOn(Module.prototype as any, 'privateFunction');
myPrivateFunc.mockImplementation(() => {});
// Act
module.publicFunction();
// Assert
expect(myPrivateFunc).toHaveBeenCalled();
});
});
Upvotes: 47
Reputation: 3304
If you are calling a function (funA
) inside another function (funB
) but you want your tests to use a mocked version of funA
, refactor funA
to use a class for its implementation, then mock a method on the class' prototype.
Suppose you have this:
async function goodsDetails({ code }) {
return goodsNameFor(code);
}
async function goodsNameFor(code) {
// ... perform long running operation
return 'xyz'
}
If you want to mock goodsNameFor
so that when you're testing goodsDetails
function you use the goodsNameFor, then change the implementation of goodsNameFor
to be like this:
class GoodsNameCommand {
constructor({ code }) {
this.code = code;
}
async executeAsync() {
// ... perform long running operation
return 'xyz'
}
}
async function goodsNameFor(code) {
// ... perform long running operation
return new GoodsNameCommand().executeAsync();
}
Then at the top of your test class have this:
requiredJest.spyOn(GoodsNameCommand.prototype, 'executeAsync')
.mockResolvedValue('Webstorm');
Upvotes: 2
Reputation: 672
The most simple way I found to spyOn
private methods using ts-jest
.
First assume following example class with private method doStuff
you wanna spy on:
class Example{
private doStuff(): void {
...doing stuff
}
}
const example = new Example();
Then is how you write a spy
jest.spyOn(example as any, 'doStuff');
Upvotes: 2
Reputation: 2243
Another option would be an explicit cast:
const spy = jest.spyOn((someInstance as unknown) as { privateMethod: SomeClass['privateMethod'] }, 'privateMethod');
It's a bit lengthy, but it has the advantage of keeping the typing of the private method for subsequent checks like spy.hasBeenCalledWith(...)
.
Upvotes: 9
Reputation: 1715
I found a way to mock my private function by using the babel-plugin-rewire
module.
In package.json
I have the following:
"devDependencies": {
...
"babel-plugin-rewire": "1.0.0-beta-5",
"babel-jest": "18.0.0",
...
In .babel.rc
I have the following:
{
"presets": [
"es2015",
"stage-0",
"react"
],
"env": {
"test": {
"plugins": [
"babel-plugin-rewire"
]
}
},
...
At this point I was able to mock the private function:
import * as moduleToTest from './moduleToTest.js'
describe('#publicFunction', () => {
it('mocks private function', () => {
moduleToTest.__Rewire__('privateFunction', () => {
console.log('I am the mocked private function');
})
...
})
})
Upvotes: 26
Reputation: 110892
There is no way through the nature of JavaScript. The function is bound to the scope of the module, so there is no way to know that this function exists from the outside, so no way to access the function and in the end no way to mock it.
Maybe more important, you should not test on the internals of the object under test but only the public API. Cause that is everything that counts. No one cares how stuff is done internally as long as the public API stays stable.
Upvotes: 8