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.
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.
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)
})
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
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!