Python like function mock in JavaScript - javascript

With Python it's so easy to mock a function that is used in the function under test.
# my_module.py
def function_a():
return 'a'
def function_b():
return function_a() + 'b'
# tests/test_my_module.py
from unittest import TestCase
from unittest.mock import patch
from my_module import function_b
class MyModuleTestCase(TestCase):
#patch('my_module.function_a')
def test_function_b(self, mock_function_a):
mock_function_a.return_value = 'c'
self.assertEqual(function_b(), 'cb')
Is something like this possible in JavaScript using, for example, jest?
# myModule.js
function myFunctionA() {
return 'a';
}
export function myFunctionB() {
return myFunctionA() + 'b';
}
# __test__/test.myModule.js
import { myFunctionB } from '../myModule';
describe('myModule tests', () => {
test('myFunctionB works', () => {
// Mock `myFunctionA` return value here somehow.
expect(myFunctionB()).toBe('cb')
});
});
I've read https://github.com/facebook/jest/issues/936 and still have no idea how to do it as there are so many (hacky) suggestions (some of them ~2 years old).

Jest can mock an entire module using jest.mock() or mock an individual module export using jest.spyOn() in combination with functions like mockImplementation().
This makes it easy to mock a function imported from a library:
// ---- lib.js ----
export function myFunctionA() {
return 'a';
}
// ---- myModule.js ----
import { myFunctionA } from './lib';
export function myFunctionB() {
return myFunctionA() + 'b'; // call lib.myFunctionA()
}
// ---- myModule.test.js ----
import { myFunctionB } from './myModule';
import * as lib from './lib';
describe('myModule tests', () => {
test('myFunctionB works', () => {
const mock = jest.spyOn(lib, 'myFunctionA'); // create a spy on lib.myFunctionA()
mock.mockImplementation(() => 'c'); // replace the implementation
expect(myFunctionB()).toBe('cb');
mock.mockRestore(); // remove the spy and mock implementation
});
});
In the code sample from the question myModule contains two functions and one calls the other directly.
Because a mock works on either an entire module or a module export, mocking the direct call to myFunctionA() from within myFunctionB() would be very difficult the way the code is written.
The easiest way I have found to work around situations like this is to import the module into itself and use the module when calling the function. That way it is the module export that is being called and it can be mocked in the test:
// ---- myModule.js ----
import * as myModule from './myModule';
export function myFunctionA() {
return 'a';
}
export function myFunctionB() {
return myModule.myFunctionA() + 'b'; // call myModule.myFunctionA()
}
// ---- myModule.test.js ----
import * as myModule from './myModule';
describe('myModule tests', () => {
test('myFunctionB works', () => {
const mock = jest.spyOn(myModule, 'myFunctionA'); // create a spy on myModule.myFunctionA()
mock.mockImplementation(() => 'c'); // replace the implementation
expect(myModule.myFunctionB()).toBe('cb');
mock.mockRestore(); // remove the spy and mock implementation
});
});

Related

jest.mock not working with Javascript test and Typescript module

My mocked utilFunction isn't being used and adding logging to the factory function shows that it's never called. I've already tried searching for jest.mock not working with relative paths and jest.mock not being called for Typescript thinking that it might be related to the mix of JS tests and TS source code or to the different module paths used in the source vs test code.
Code being tested:
// src/foo/fooModule.ts
import { utilFunction } from '../util'
export const foo = () => {
return utilFunction()
}
Test code:
// test/fooModule.test.js
const { foo } = require('../src/foo/fooModule')
jest.mock('../src/util', () => {
return { utilFunction: () => 'mocked' };
});
describe('fooModule tests', () => ...)
The jest.mock call needs to be moved above the imports:
// test/fooModule.test.js
jest.mock('../src/util', () => {
return { utilFunction: () => 'mocked' };
});
const { foo } = require('../src/foo/fooModule')
describe('fooModule tests', () => ...)
My last experience working with Jest prior to this was in a project where the tests were also written in Typescript and babel-jest was used. babel-jest includes babel-jest-hoist which hoists the jest mocks above any imports automatically, so I didn't previously have to worry about the ordering.

How to write a webpack plugin to replace require function to its source code

Say we have a util.js contains functions a and b:
// util.js
export function a() {
...
}
export function b() {
...
}
And I require them in index.js:
// index.js
export default function main() {
const {a, b} = require('./util');
...
a(); // use them somewhere
b();
}
But I want to replace the require function to its source code like this before the bundling:
// replaced index.js
export default function main() {
const a = function () {
// ... source code from util.js
};
const b = function () {
// ... source code from util.js
};
...
a(); // use them somewhere
b();
}
I'm not that familiar with webpack plugins and hooks API.
Is this possible and if yes, how to do it?
Thanks!
One solution not that clever, do string replace directly by string-replace-loader.

Jest test module while mocking function from same module

I am trying to verify that a function within a module calls another function within that module. When I try the following, Jest reports that bar was called 0 times. How can I test the function call successfully?
// module.js
function foo() {
bar()
}
function bar() {
...
}
export {foo, bar}
// __tests__/module-test.js
import * as module from "../module";
test("foo calls bar", () => {
module.bar = jest.fn();
module.foo();
expect(module.bar).toHaveBeenCalledTimes(1)
})

how to deal with jest mock function of a module and typescript type

I use ts-jest and jest to write my testing files with typescript.
I am confused how to typing the mock function of a module.
Here is my code:
./module.ts:
import {IObj} from '../interfaces';
const obj: IObj = {
getMessage() {
return `Her name is ${this.genName()}, age is ${this.getAge()}`;
},
genName() {
return 'novaline';
},
getAge() {
return 26;
}
};
export default obj;
./module.test.ts:
import * as m from './module';
describe('mock function test suites', () => {
it('t-1', () => {
// I think the jest.Mock<string> type here is not correct.
m.genName: jest.Mock<string> = jest.fn(() => 'emilie');
expect(jest.isMockFunction(m.genName)).toBeTruthy();
expect(m.genName()).toBe('emilie');
expect(m.getMessage()).toEqual('Her name is emilie, age is 26');
expect(m.genName).toHaveBeenCalled();
});
});
how to type the mock function genName of module m?
typescript give me an error here:
Error:(8, 7) TS2540:Cannot assign to 'genName' because it is a constant or a read-only property.
This is how I have solved the same problem and how I do all of my mocking and spying now.
import * as m from './module';
describe('your test', () => {
let mockGenName;
beforeEach(() => {
mockGenName = jest.spyOn(m,
'genName').mockImplemetation(() => 'franc');
})
afterEach(() => {
mockGenName.mockRestore();
})
test('your test description', () => {
// do something that calls the genName function
expect(mockGenName).toHaveBeenCalledTimes(1);
})
})
With this setup, you can programmatically change the implementation of the mock for different tests, and then assert that the function was called and what it was called with, all while clearing your mock in between tests and after all tests.
Try this one - https://jestjs.io/docs/mock-function-api#typescript
In short there are only three strategies possible
Mock the entire module being imported and get handler to the mocked function to manipulate it (jest.Mock(), jest.MockedFunction)
Mock the module partially being imported and get handler to the mocked function to manipulate it (jest.Mock() with factory, jest.MockedFunction)
Import the module as is and then spy on the function which needs to be mocked (jest.spy())
You want to mock the module and then alter the exported function within it. This should replace your import statement.
jest.mock('./module', () => ({
genName: jest.fn().mockImplementation(() => 'emilie')
}))
import * as m from './module'
jest.mock('./module', () => ({
genName: jest.fn().mockImplementation(() => 'emilie')
// will return "enilie" for all tests
}))
it('returns franc', () => {
m.genName.mockImplementationOnce(() => 'franc')
// will return "franc" for this particular test
})
The reason why I got the error is:
The properties of a module object foo (import * as foo from 'foo') are like the properties of a frozen object.
For more info, see In ES6, imports are live read-only views on exported values
When I changed import * as m from './module' to import m from './module';, The error is gone.
Package versions:
"typescript": "^3.6.4"
"ts-jest": "^24.1.0"
"jest": "^24.9.0",
jest.config.js:
module.exports = {
preset: 'ts-jest/presets/js-with-ts',
//...
}
tsconfig.json:
"compilerOptions": {
"target": "es6",
"module": "commonjs",
//...
}
I'm mocking it a follow :
jest.mock('./module')
const {genName} = require('./module')
and in my test :
genName.mockImplementationOnce(() => 'franc')
works great for me and no typescript errors

JavaScript ES6 Module OnLoad handler implementation

I have a NodeJS server application which is split to lost of ES6 modules. I am trying to create a kind of "load module handler", a function in the main module, which other modules would require to register a callback, which will be executed after the main module is fully initialized. I am using Babel (with babel-preset-es2015) to transpile ES6 modules into executable JavaScript.
To demonstrate the issue here in short, I've created 2 sample files.
File index.js (application entry, main module):
import * as js2 from "./js2.js";
let toCall = [], // this array handles callbacks from other modules
initialized = false; // flag
export function onInit (cb) { // registers cb to execute after this module is initialized
if (initialized) {
cb();
return;
}
toCall.push(cb);
}
function done () { // initialization is done - execute all registered callbacks
toCall.forEach(f => f());
}
// some important stuff here
// callback(() => {
initialized = true;
done();
// });
And the other module js2.js:
import { onInit } from "./index";
onInit(() => {
console.log("Now I can use initialized application!");
});
All seems to be OK to me, but unfortunately this doesn't work throwing the next error in the first file:
Cannot read property 'push' of undefined
Thing is, there is no toCall variable at this point, but why? Variable toCall is declared before onInit function, it must be ready to use in onInit, mustn't it? How to solve this and is my way rational enough to implement something called "module initialization callbacks"? Are there any other solutions for this?
Thank you for any help and advice.
I found a beautiful implementation for this.
It is needed to separate "onload handler" implementation to individual module. As a result of this example, there will be three files:
index.js:
import * as js2 from "./js2.js";
import { initDone } from "./init.js";
// some important stuff here
// callback(() => {
console.log(`Main module is initialized!`);
initDone();
// });
js2.js:
import { onInit } from "./init.js";
onInit(() => {
console.log("Module js2.js is initialized!");
});
init.js:
let toCall = [], // this array has to handle functions from other modules
initialized = false; // init flag
export function onInit (cb) {
if (initialized) {
cb();
return;
}
toCall.push(cb);
}
export function initDone () {
initialized = true;
toCall.forEach(f => f());
}
And the result:
Main module is initialized!
Module js2.js is initialized!

Categories