Mock dependency in Jest with TypeScript - javascript

When testing a module that has a dependency in a different file and assigning that module to be a jest.mock, TypeScript gives an error that the method mockReturnThisOnce (or any other jest.mock method) does not exist on the dependency, this is because it is previously typed.
What is the proper way to get TypeScript to inherit the types from jest.mock?
Here is a quick example.
Dependency
const myDep = (name: string) => name;
export default myDep;
test.ts
import * as dep from '../depenendency';
jest.mock('../dependency');
it('should do what I need', () => {
//this throws ts error
// Property mockReturnValueOnce does not exist on type (name: string)....
dep.default.mockReturnValueOnce('return')
}
I feel like this is a very common use case and not sure how to properly type this.

You can use type casting and your test.ts should look like this:
import * as dep from '../dependency';
jest.mock('../dependency');
const mockedDependency = <jest.Mock<typeof dep.default>>dep.default;
it('should do what I need', () => {
//this throws ts error
// Property mockReturnValueOnce does not exist on type (name: string)....
mockedDependency.mockReturnValueOnce('return');
});
TS transpiler is not aware that jest.mock('../dependency'); changes type of dep thus you have to use type casting. As imported dep is not a type definition you have to get its type with typeof dep.default.
Here are some other useful patterns I've found during my work with Jest and TS
When imported element is a class then you don't have to use typeof for example:
import { SomeClass } from './SomeClass';
jest.mock('./SomeClass');
const mockedClass = <jest.Mock<SomeClass>>SomeClass;
This solution is also useful when you have to mock some node native modules:
import { existsSync } from 'fs';
jest.mock('fs');
const mockedExistsSync = <jest.Mock<typeof existsSync>>existsSync;
In case you don't want to use jest automatic mock and prefer create manual one
import TestedClass from './TestedClass';
import TestedClassDependency from './TestedClassDependency';
const testedClassDependencyMock = jest.fn<TestedClassDependency>(() => ({
// implementation
}));
it('Should throw an error when calling playSomethingCool', () => {
const testedClass = new TestedClass(testedClassDependencyMock());
});
testedClassDependencyMock() creates mocked object instance
TestedClassDependency can be either class or type or interface

Use the mocked helper
like explained here
// foo.spec.ts
import { foo } from './foo'
jest.mock('./foo')
// here the whole foo var is mocked deeply
const mockedFoo = jest.mocked(foo, true)
test('deep', () => {
// there will be no TS error here, and you'll have completion in modern IDEs
mockedFoo.a.b.c.hello('me')
// same here
expect(mockedFoo.a.b.c.hello.mock.calls).toHaveLength(1)
})
test('direct', () => {
foo.name()
// here only foo.name is mocked (or its methods if it's an object)
expect(jest.mocked(foo.name).mock.calls).toHaveLength(1)
})

There are two solutions tested for TypeScript version 3.x and 4.x, both are casting desired function
1) Use jest.MockedFunction
import * as dep from './dependency';
jest.mock('./dependency');
const mockMyFunction = dep.myFunction as jest.MockedFunction<typeof dep.myFunction>;
2) Use jest.Mock
import * as dep from './dependency';
jest.mock('./dependency');
const mockMyFunction = dep.default as jest.Mock;
There is no difference between these two solutions. The second one is shorter and I would therefore suggest using that one.
Both casting solutions allows to call any jest mock function on mockMyFunction like mockReturnValue or mockResolvedValue
https://jestjs.io/docs/en/mock-function-api.html
mockMyFunction.mockReturnValue('value');
mockMyFunction can be used normally for expect
expect(mockMyFunction).toHaveBeenCalledTimes(1);

I use the pattern from #types/jest/index.d.ts just above the type def for Mocked (line 515):
import { Api } from "../api";
jest.mock("../api");
const myApi: jest.Mocked<Api> = new Api() as any;
myApi.myApiMethod.mockImplementation(() => "test");

Cast as jest.Mock
Simply casting the function to jest.Mock should do the trick:
(dep.default as jest.Mock).mockReturnValueOnce('return')

Use as jest.Mock and nothing else
The most concise way of mocking a module exported as default in ts-jest that I can think of really boils down to casting the module as jest.Mock.
Code:
import myDep from '../dependency' // No `* as` here
jest.mock('../dependency')
it('does what I need', () => {
// Only diff with pure JavaScript is the presence of `as jest.Mock`
(myDep as jest.Mock).mockReturnValueOnce('return')
// Call function that calls the mocked module here
// Notice there's no reference to `.default` below
expect(myDep).toHaveBeenCalled()
})
Benefits:
does not require referring to the default property anywhere in the test code - you reference the actual exported function name instead,
you can use the same technique for mocking named exports,
no * as in the import statement,
no complex casting using the typeof keyword,
no extra dependencies like mocked.

The latest jest allows you to do this very easily with jest.mocked
import * as dep from '../dependency';
jest.mock('../dependency');
const mockedDependency = jest.mocked(dep);
it('should do what I need', () => {
mockedDependency.mockReturnValueOnce('return');
});

Here's what I did with jest#24.8.0 and ts-jest#24.0.2:
source:
class OAuth {
static isLogIn() {
// return true/false;
}
static getOAuthService() {
// ...
}
}
test:
import { OAuth } from '../src/to/the/OAuth'
jest.mock('../src/utils/OAuth', () => ({
OAuth: class {
public static getOAuthService() {
return {
getAuthorizationUrl() {
return '';
}
};
}
}
}));
describe('createMeeting', () => {
test('should call conferenceLoginBuild when not login', () => {
OAuth.isLogIn = jest.fn().mockImplementationOnce(() => {
return false;
});
// Other tests
});
});
This is how to mock a non-default class and it's static methods:
jest.mock('../src/to/the/OAuth', () => ({
OAuth: class {
public static getOAuthService() {
return {
getAuthorizationUrl() {
return '';
}
};
}
}
}));
Here should be some type conversion from the type of your class to jest.MockedClass or something like that. But it always ends up with errors. So I just used it directly, and it worked.
test('Some test', () => {
OAuth.isLogIn = jest.fn().mockImplementationOnce(() => {
return false;
});
});
But, if it's a function, you can mock it and do the type conversation.
jest.mock('../src/to/the/Conference', () => ({
conferenceSuccessDataBuild: jest.fn(),
conferenceLoginBuild: jest.fn()
}));
const mockedConferenceLoginBuild = conferenceLoginBuild as
jest.MockedFunction<
typeof conferenceLoginBuild
>;
const mockedConferenceSuccessDataBuild = conferenceSuccessDataBuild as
jest.MockedFunction<
typeof conferenceSuccessDataBuild
>;

As of Jest 24.9.0 here is how you can mock and correctly type both your Class/Object/function and Jest properties.
jest.MockedFunction
jest.MockedClass
What we would like for a typed mock is that the mocked object type contains the union of the mocked object type and the type of Jest mocks.
import foo from 'foo';
jest.mock('foo');
const mockedFoo = foo as jest.MockedFunction<typeof foo>;
// or: const mockedFooClass = foo as jest.MockedClass<typeof FooClass>;
mockedFoo.mockResolvedValue('mockResult');
// Or:
(mockedFoo.getSomething as jest.MockedFunction<typeof mockedFoo.getSomething>).mockResolvedValue('mockResult');
As you can see, you can either manually cast what you need or you'll need something to traverse all foo's properties/methods to type/cast everything.
To do that (deep mock types) you can use jest.mocked() introduced in Jest 27.4.0
import foo from 'foo';
jest.mock('foo');
const mockedFoo = jest.mocked(foo, true);
mockedFoo.mockImplementation() // correctly typed
mockedFoo.getSomething.mockImplementation() // also correctly typed

I have found this in #types/jest:
/**
* Wrap a function with mock definitions
*
* #example
*
* import { myFunction } from "./library";
* jest.mock("./library");
*
* const mockMyFunction = myFunction as jest.MockedFunction<typeof myFunction>;
* expect(mockMyFunction.mock.calls[0][0]).toBe(42);
*/
Note: When you do const mockMyFunction = myFunction and then something like mockFunction.mockReturnValue('foo'), you're a changing myFunction as well.
Source: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/jest/index.d.ts#L1089

The top rated solution by Artur Górski does not work with the last TS and Jest.
Use MockedClass
import SoundPlayer from '../sound-player';
jest.mock('../sound-player'); // SoundPlayer is now a mock constructor
const SoundPlayerMock = SoundPlayer as jest.MockedClass<typeof SoundPlayer>;

A recent library solves this problem with a babel plugin: https://github.com/userlike/joke
Example:
import { mock, mockSome } from 'userlike/joke';
const dep = mock(import('./dependency'));
// You can partially mock a module too, completely typesafe!
// thisIsAMock has mock related methods
// thisIsReal does not have mock related methods
const { thisIsAMock, thisIsReal } = mockSome(import('./dependency2'), () => ({
thisIsAMock: jest.fn()
}));
it('should do what I need', () => {
dep.mockReturnValueOnce('return');
}
Be aware that dep and mockReturnValueOnce are fully type safe. On top, tsserver is aware that depencency was imported and was assigned to dep so all automatic refactorings that tsserver supports will work too.
Note: I maintain the library.

This is ugly, and in fact getting away from this ugliness is why I even looked at this question, but to get strong typing from a module mock, you can do something like this:
const myDep = (require('./dependency') as import('./__mocks__/dependency')).default;
jest.mock('./dependency');
Make sure you require './dependency' rather than the mock directly, or you will get two different instantiations.

For me this was enough:
let itemQ: queueItemType
jest.mock('../dependency/queue', () => {
return {
add: async (item: queueItemType, ..._args: any) => {
// then we can use the item that would be pushed to the queue in our tests
itemQ = item
return new Promise(resolve => {
resolve('Mocked')
})
},
}
})
Then, whenever the add method is called it will execute this code above instead of pushing it to the queue, in this case.

With TypeScript 2.8 we can do like this with ReturnType:
import * as dep from "./depenendency"
jest.mock("./dependency")
const mockedDependency = <jest.Mock<ReturnType<typeof dep.default>>>dep.default
it("should do what I need", () => {
mockedDependency.mockReturnValueOnce("return")
})

Related

Mocking / Spying helper function in Angular Unit tests

I have a helper file with multiple function:
myHelpers.ts
export function fn1(a, b) {
return a * b;
}
export function fn2(a, b) {
return a + b;
}
export function fn3(a, b) {
return a - b;
}
The above file, as well are others are bundled as a module and exported in the index.ts as
import * as Helpers from '/myHelpers';
import * as DaoUtils from '/daoUtils';
export { Helpers };
export { DaoUtils };
I consume the above module in my app package.json as:
"helper-utils": "file:/local/app/helper"
Which my angular component uses:
import { Helpers } from 'helper-lib';
export class MyComponent {
constructor() {}
public btnClick(p1, p2) {
let value = Helpers.fn1(p1, p2);
console.log(value);
return value;
}
}
The above works fine and if on btnClick() in the GUI i pass 1 and 2, i see the value of 2 in the console.log.
Unit test:
import { ComponentFixture, TestBed, waitForAsync } from '#angular/core/testing';
import { MyComponent } from './myComponent';
import { Helpers } from 'helper-lib';
describe('MyComponent', () => {
let component: MyComponent;
let fixture: ComponentFixture<MyComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({declarations:[MyComponent]}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(MyComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should calc value', () => {
let mySpy = spyOn(Helpers, 'fn1');
let value = component.btnClick(1, 2);
expect(mySpy).toHaveBeenCalled();
});
});
Error:
Error: <spyOn> : fn1 is not declared writable or has no setter
Unfortunately, I am thinking you're coming across this issue: https://stackoverflow.com/a/62935131/7365461.
As TypeScript advanced, most likely the answers linked will not work anymore.
The answer linked and the Github thread linked in the answer explains this.
An unfortunate reality with Angular is that to make testing easier, you should always be thinking about dependency injection (in essence, write re-usable code that can be used everywhere in a service). This way, it becomes easier to test.
The answer I have linked creates a class with a static method that calls the functions and then you can spy on these static methods as well. This way works as well.
If your helpers are pure functions (always return the same result if the same arguments are passed in) then it is perfectly okay not to spy on them and instead expect what the final value should be. This does very heavily tie your tests to the helper, but normally there is only one way to do certain calculations and the helper would have its own spec to prove any complicated logic, where your component would likely only use simpler values.
it('should calc value', () => {
let value = component.btnClick(1, 2);
expect(value).toBe(2);
});
If that isn't the case one other option is to create a new helpers file that looks roughly like
export class Helpers {
static fn1(a: number, b: number) {
return a * b;
}
static fn2(a: number, b: number) {
return a + b;
}
static fn3(a: number, b: number) {
return a - b;
}
}
Now your test can spy on it all it wants
it('should calc value', () => {
const mySpy = spyOn(Helpers, 'fn1').and.callThrough();
const value = component.btnClick(1, 2);
expect(mySpy).toHaveBeenCalled();
expect(value).toBe(2);
});

TypeError: _API.default is not a constructor with Jest tests

I have an API class that I am trying to use in a React app.
// API file
class API {
...
}
export default API;
// Other file
import API from "utils/API";
const api = new API();
And I am getting the error:
TypeError: _API.default is not a constructor
But.. it seems like my default is set?
My Jest setup is like this:
"jest": {
"setupFiles": [
"./jestSetupFile.js"
],
"testEnvironment": "jsdom",
"preset": "jest-expo",
"transformIgnorePatterns": [
"node_modules/(?!((jest-)?react-native|#react-native(-community)?)|expo(nent)?|#expo(nent)?/.*|#expo-google-fonts/.*|react-navigation|#react-navigation/.*|#unimodules/.*|unimodules|sentry-expo|native-base|react-native-svg|react-router-native/.*|#invertase/react-native-apple-authentication/.*)"
]
},
My strong guess is that this is due to a configuration of my babel, webpack or package.json.
What could be causing this?
Note, I want to be clear, this doesn't happen whatsoever in my main application, only in Jest testing
If I change it to a named export/import, I get this:
TypeError: _API.API is not a constructor
Extremely confusing behavior.
As mentioned by others, it would be helpful to see a minimum reproducible example.
However, there is one other possible cause. Are you mocking the API class in your test file at all? This problem can sometimes happen if a class is mistakenly mocked as an "object" as opposed to a function. An object cannot be instantiated with a "new" operator.
For example, say we have a class file utils/API like so:
class API {
someMethod() {
// Does stuff
}
}
export default API;
The following is an "incorrect" way to mock this class and will throw a TypeError... is not a constructor error if the class is instantiated after the mock has been created.
import API from 'utils/API';
jest.mock('utils/API', () => {
// Returns an object
return {
someMethod: () => {}
};
})
// This will throw the error
const api = new API();
The following will mock the class as a function and will accept the new operator and will not throw the error.
import API from 'utils/API';
jest.mock('utils/API', () => {
// Returns a function
return jest.fn().mockImplementation(() => ({
someMethod: () => {}
}));
})
// This will not throw an error anymore
const api = new API();
Trying adding "esModuleInterop": true, in your tsconfig.json. BY default esModuleInterop is set to false or is not set. B setting esModuleInterop to true changes the behavior of the compiler and fixes some ES6 syntax errors.
Refer the documentation here.
This was ultimately due to additional code inside the file that I was exporting the class from.
import { store } from "root/App";
if (typeof store !== "undefined") {
let storeState = store.getState();
let profile = storeState.profile;
}
At the top, outside my class for some functionality I had been working on.
This caused the class default export to fail, but only in Jest, not in my actual application.
You'll need to export it like this :
export default class API
You could try with:
utils/API.js
export default class API {
...
}
test.js
import API from "utils/API";
const api = new API();
I'm adding this because the issue I had presented the same but has a slightly different setup.
I'm not exporting the class with default, i.e.
MyClass.ts
// with default
export default MyClass {
public myMethod()
{
return 'result';
}
}
// without default, which i'm doing in some instances.
export MyClass {
public myMethod()
{
return 'result';
}
}
When you don't have the default, the import syntax changes.
In a (jest) test if you follow the convention where you do have export default MyClass(){};
then the following works.
const MOCKED_METHOD_RESULT = 'test-result'
jest.mock("MyClass.ts", () => {
// will work and let you check for constructor calls:
return jest.fn().mockImplementation(function () {
return {
myMethod: () => {
return MOCKED_METHOD_RESULT;
},
};
});
});
However, if you don't have the default and or are trying to mock other classes etc. then you need to do the following.
Note, that the {get MyClass(){}} is the critical part, i believe you can swap out the jest.fn().mockImplementation() in favour of jest.fn(()=>{})
jest.mock("MyClass.ts", () => ({
get MyClass() {
return jest.fn().mockImplementation(function () {
return {
myMethod: () => {
return MOCKED_METHOD_RESULT;
},
};
});
},
}));
So the issue is the way in which you access the contents of the class your mocking. And the get part allows you to properly define class exports.
I resolved this error by using below code.
jest.mock('YOUR_API_PATH', () => ({
__esModule: true,
default: // REPLICATE YOUR API CONSTRUCTOR BEHAVIOUR HERE BY ADDING CLASS
})
If you want to mock complete API class, please check the below snippet.
jest.mock('YOUR_API_PATH', () => ({
__esModule: true,
default: class {
constructor(args) {
this.var1 = args.var1
}
someMethod: jest.fn(() => Promise.resolve())
},
}));

Jest - Pass mocked instance as class constructor argument

I'm currently trying to write some test with jest (newbie). Regarding to my latest post, I've got the following situation:
I've got multiple price fields. Each price field has its own renderer. A renderer has a reference to its pricefield. A pricefield can determine some html snippet from the dom.
// priceField.js
class PriceField {
constructor() {
const renderer = new PriceFieldRenderer(this);
// do something with renderer
}
/**
* Determines Node in DOM for this price field
*/
determinePriceFieldNode() {
return document.querySelector(".price-field");
}
}
export default PriceField;
// priceFieldRenderer.js
class PriceFieldRenderer {
constructor(priceField) {
this.priceFieldNode = priceField.determinePriceFieldNode();
}
}
export default PriceFieldRenderer;
Now I want to test PriceFieldRenderer (with jest). Therefore, I need to mock PriceField and its determinePriceFieldNode() function.
However, I'm not able to pass a mocked price field instance as constructor argument. I've read the official documentation with its sound-player example and googled about 2 hours. Also i tested different implementations, but I'm not able to solve it. Here is one code snippet which throws following error: TypeError: priceField.determinePriceFieldNode is not a function
jest.mock("./priceField");
import PriceFieldRenderer from "./priceFieldRenderer";
import PriceField from "./priceField";
describe("Price field renderer", () => {
test("with calculated price", () => {
const priceField = PriceField.mockImplementation(() => {
return {
determinePriceFieldNode: () => "<html/>"
};
});
const sut = new PriceFieldRenderer(priceField);
expect(sut).toBeDefined();
});
});
I'm sure the way I did is not the best and there is some help out there :)
The problem here is that priceField is lowercase and makes an assumption that it's an instance, while it's spy function that should create an instance. It can be fixed with:
PriceField.mockImplementation(...);
const priceField = new PriceField();
const sut = new PriceFieldRenderer(priceField);
priceField module mock and and associated PriceField spy serve no good purpose. They would be needed if it were imported and used in another module, this is the said example from Jest documentation shows.
That PriceFieldRenderer uses dependency injection makes testing simpler, this is one of benefits of the pattern:
const priceField = { determinePriceFieldNode: jest.fn(() => "<html/>") });
const sut = new PriceFieldRenderer(priceField);

Jest mock window object before importing a dependency

I need to have a value set in the window object before a dependency is imported. Say I have this code
// foo.test.js
import { dependency } from './foo'
describe('...', () => {
it('...', () => {
// use dependency
})
})
But for dependency to be imported I need to have a value defined in window.myValues
// foo.js
export const dependency = {
key: window.myValue.nestedValue
}
That code will give me an error when importing the file because window.myValue.nestedValue is trying to access the property nestedValue of undefined.
How can I get that done?
Edit
Following christianeide's answer below I get the following error
● Test suite failed to run
TypeError: Cannot convert undefined or null to object
2 | delete global.window.myValue
3 | global.window = Object.create(window)
> 4 | global.window.myValue = {
| ^
5 | nestedValue: 'someValue'
6 | }
7 | }
at module.exports (jest.setup.js:4:17)
at node_modules/#jest/core/build/runGlobalHook.js:82:17
at ScriptTransformer.requireAndTranspileModule (node_modules/#jest/transform/build/ScriptTransformer.js:684:24)
at node_modules/#jest/core/build/runGlobalHook.js:72:27
at pEachSeries (node_modules/p-each-series/index.js:8:9)
at async _default (node_modules/#jest/core/build/runGlobalHook.js:58:5)
at async runJest (node_modules/#jest/core/build/runJest.js:345:5)
es6 imports are "hoisted", meaning wherever you write them in the code, they'll get processed before the importing module is executed, so the imported module is always executed before the importing module. In your case, this means foo.js executes before foo.test.js, so even if you properly mock your property of window in your tests, foo.js will not see your mock.
You can get around this by using require in your tests to import foo.js after the property of window has been mocked.
// foo.test.js
window.myValue = { nestedValue: MOCK_NESTED_VALUE };
const { dependency } = require('./foo');
describe('...', () => {
it('...', () => {
// use dependency
})
})
As other answers have pointed out, if myValue is one of existing "sytem" properties of window such as window.location, you might have to delete it first. When deleting, don't forget to back it up so that you can restore it when cleaning up after your test.
Try assigning values to global.window.
Something like this:
delete global.window.myValue;
global.window = Object.create(window);
global.window.myValue = {
nestedValue: 'someValue',
};
This can be done inside jest.setup.js, but you could also probably define the values inside foo.test.js
I was able to append properties on the window object by creating a setup.js:
global.propertyA = () => {};
global.nestedPropertyB = {
propertyC: () => {}
};
And setting the file in setupFilesAfterEnv in my jest.config.js file:
module.exports = {
setupFilesAfterEnv: ['<rootDir>/tests/js/setup.js']
}
I have solved such an issue with jest.mock: as it's hoisted above import statements, it's possible to execute any code before and after importing an actual module.
In my case I needed to test some functionality and set node env to a value, different from 'test', and, as I needed this substitution only for the time of import, I set its value back to 'test' before returning the actual module. Of course, it may vary depending on use cases - it's just an example of executing code before and after importing is done. Also i tried to use a getter for a particular module field, and it also worked.
import ApiBase from "../ApiBase";
jest.mock('../ApiBase', () => {
process.env.NODE_ENV = '';
const actual = jest.requireActual('../ApiBase');
process.env.NODE_ENV = 'test';
return actual;
});

Manual mock not working in with Jest

Can somebody help me with manual mocking in Jest, please? :)
I try to have Jest use the mock instead of the actual module.
my test:
// __tests__/mockTest.js
import ModuleA from "../src/ModuleA"
describe("ModuleA", () => {
beforeEach(() => {
jest.mock("../src/ModuleA")
})
it("should return the mock name", () => {
const name = ModuleA.getModuleName()
expect(name).toBe("mockModuleA")
})
})
my code:
// src/ModuleA.js
export default {
getModuleName: () => "moduleA"
}
// src/__mocks__/ModuleA.js
export default {
getModuleName: () => "mockModuleA"
}
I think I followed everything the documentation says about manual mocks, but perhaps I'm overlooking something here?
This is my result:
Expected value to be:
"mockModuleA"
Received:
"moduleA"
Module mocks are hoisted when possible with babel-jest transform, so this will result in mocked module:
import ModuleA from "../src/ModuleA"
jest.mock("../src/ModuleA") // hoisted to be evaluated prior to import
This won't work if a module should be mocked per test basis, because jest.mock resides in beforeEach function.
In this case require should be used:
describe("ModuleA", () => {
beforeEach(() => {
jest.mock("../src/ModuleA")
})
it("should return the mock name", () => {
const ModuleA = require("../src/ModuleA").default;
const name = ModuleA.getModuleName()
expect(name).toBe("mockModuleA")
})
})
Since it's not an export but a method in default export that should be mocked, this can also be achieved by mocking ModuleA.getModuleName instead of entire module.
This answer is not strictly related to the OPs question but google lead me here for a similar issue where jest.mock('module', () => ({})) in the root of a file did not work. In my case it was caused by cyclic dependencies. So if jest suddenly starts to ignore calls to jest.mock() then you might want to check for cyclic dependencies in your files.

Categories

Resources