I have yet to see any full answer/example for this. In my TypeScript project, I have a Class that has a constructor, a function, and a static function. All three are invoked in the code under test. I'd like to write a unit test using Jest that allows me to check the mocked constructor, mocked function, and mocked static function.
I typically use the mock factory approach for mocking Classes (like below); however, I cannot seem to get this to work.
// ClassA.ts
export class ClassA {
constructor() {
// construction logic
}
public async func1(x: int): Promise<void> {
// func1 logic
}
static func2(x: string | number): x is string {
// func2 logic
let conditionIsMet: boolean;
// conditionIsMet logic
if (conditionIsMet) {
return true;
}
return false;
}
}
// CodeUnderTest.ts
import { ClassA } from './ClassA';
export class ClassB {
public async foo() {
if (ClassA.func2('asdf')) {
const a = new ClassA();
await a.func1(45);
}
}
}
// UnitTest.ts
import { ClassB } from './ClassB';
// mock factory
const mockFunc1 = jest.fn();
const mockStaticFunc2 = jest.fn();
jest.mock('./ClassA', () => ({
ClassA: jest.fn().mockImplementation(() => ({
func1: mockFunc1,
func2: mockStaticFunc2, // <= this doesn't work
}),
});
beforeAll(() => {
mockStaticFunc2.mockReturnValue(true);
});
describe('when ClassB.foo() is called', () => {
describe('when ClassA.func2 is given a string parameter', () => {
it('then ClassA.func2 is invoked with expected parameters, ClassA is constructed, ClassA.func1 is invoked with expected parameters', () => {
// arrange
const clsB = new ClassB();
// execute
clsB.foo();
// expect
expect(mockStaticFunc2).toHaveBeenCalledTimes(1);
expect(mockStaticFunc2).toHaveBeenNthCalledWith(1, 'asdf');
expect(ClassA).toHaveBeenCalledTimes(1); // constructor was mocked
expect(mockFunc1).toHaveBeenCalledTimes(1);
expect(mockFunc1).toHaveBeenNthCalledWith(1, 45);
});
});
});
Are there any complete examples that illustrates how I can propertly mock all three (ClassA construtor, ClassA.func1 instance function, and ClassA.func2 static function)?
You didn't mock the static method func2
correctly. The static methods are accessed on the class itself. Besides, take a look es6-class-mocks#automatic-mock
Here is an example:
ClassA.ts
:
export class ClassA {
constructor() {}
public async func1(x: number): Promise<string> {
return 'real value';
}
static func2(x: string | number) {
return false;
}
}
ClassB.ts
:
import { ClassA } from './ClassA';
export class ClassB {
public async foo() {
if (ClassA.func2('asdf')) {
const a = new ClassA();
const r = await a.func1(45);
console.log("🚀 ~ file: ClassB.ts:8 ~ ClassB ~ foo ~ r:", r)
}
}
}
ClassB.test.ts
:
import { ClassB } from './ClassB';
import { ClassA } from './ClassA';
// Mock each methods manually
// jest.mock('./ClassA', () => {
// const MockClassA = jest.fn(function () {
// this.func1 = jest.fn()
// });
// (MockClassA as any).func2 = jest.fn();
// return {
// ClassA: MockClassA,
// };
// });
// or, let jest create mocks for you
jest.mock('./ClassA');
describe('when ClassB.foo() is called', () => {
describe('when ClassA.func2 is given a string parameter', () => {
it('then ClassA.func2 is invoked with expected parameters, ClassA is constructed, ClassA.func1 is invoked with expected parameters', async () => {
expect(jest.isMockFunction(ClassA)).toBe(true);
expect(jest.isMockFunction(ClassA.func2)).toBe(true);
expect(jest.isMockFunction(ClassA.prototype.func1)).toBe(true);
const ClassAMocked = jest.mocked(ClassA);
// arrange
const clsB = new ClassB();
jest.mocked(ClassA.func2).mockReturnValueOnce(true);
jest.mocked(ClassA.prototype.func1).mockResolvedValueOnce('fake value');
// execute
await clsB.foo();
// expect
const classAInstance = ClassAMocked.mock.instances[0];
expect(jest.isMockFunction(classAInstance.func1)).toBe(true);
expect(ClassAMocked.func2).toHaveBeenNthCalledWith(1, 'asdf');
expect(ClassAMocked).toHaveBeenCalledTimes(1);
expect(classAInstance.func1).toHaveBeenNthCalledWith(1, 45);
});
});
});
Test result:
console.log
🚀 ~ file: ClassB.ts:8 ~ ClassB ~ foo ~ r: fake value
at ClassB.log (stackoverflow/77559902/ClassB.ts:8:15)
PASS stackoverflow/77559902/ClassB.test.ts
when ClassB.foo() is called
when ClassA.func2 is given a string parameter
✓ then ClassA.func2 is invoked with expected parameters, ClassA is constructed, ClassA.func1 is invoked with expected parameters (11 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 0.508 s, estimated 1 s
Ran all test suites related to changed files.
package versions:
"jest": "^29.7.0"