ReferenceError: Cannot access 'mockMethod1' before initialization - javascript

I have 3 source files File1.ts, File2.ts, File3.ts. While executing the Unit tests of File3 I am getting the following Error.
Test suite failed to run
ReferenceError: Cannot access 'mockMethod1' before initialization
20 | __esModule: true,
21 | default: jest.fn(),
> 22 | method1: mockMethod1,
| ^
23 | method2: mockMethod2
24 | }));
25 |
Here are the contents of the 3 source files and unit tests for File3.
File1.ts
export default class File1 {
public element;
constructor(element) {
this.element = element;
}
method1(inputs) {
// Logic of Method1.
return output;
}
method2(inputs) {
// Logic of Method2.
return output;
}
}
File2.ts
import File1 from '../Folder1/File1'
export default class File2 {
public file1Object;
constructor(element) {
this.file1Object = new File1(element);
}
method1(inputs) {
// Logic of Method1.
let out = this.file1Object.method1(inputs);
// Logic of Method1.
return output;
}
method2(inputs) {
// Logic of Method2.
let out = this.file1Object.method2(inputs);
// Logic of Method2.
return output;
}
}
File3.ts
import File2 from '../Folder2/File2'
export default class File3 {
public file2Object;
constructor(element) {
this.file2Object = new File2(element);
}
method1(inputs) {
// Logic of Method1.
let out = this.file2Object.method1(inputs);
// Logic of Method1.
return output;
}
method2(inputs) {
// Logic of Method2.
let out = this.file2Object.method1(inputs);
// Logic of Method2.
return output;
}
}
File3.test.ts
import File3 from "./File3";
import File2 from "../Folder2/File2";
const mockMethod1 = jest.fn();
const mockMethod2 = jest.fn();
jest.mock('../Folder2/File2', () => ({
__esModule: true,
default: jest.fn(),
method1: mockMethod1,
method2: mockMethod2
}));
const file3Object = new File3(inputElement);
beforeEach(() => {
jest.clearAllMocks();
});
test('Method-1 Unit Test', () => {
mockMethod1.mockReturnValue(expectedOutput);
let observedOutput = file3Object.method1(inputs);
expect(observedOutput).toBe(expectedOutput);
})
test('Method-2 Unit Test', () => {
mockMethod2.mockReturnValue(expectedOutput);
let observedOutput = file3Object.method2(inputs);
expect(observedOutput).toBe(expectedOutput);
})
I am not sure where I am making the mistake so I am unable to resolve this error. Any suggestions to resolve this issue.

There are several things that are causing trouble. First, as mentioned in jest docs:
A limitation with the factory parameter is that, since calls to jest.mock() are hoisted to the top of the file, it's not possible to first define a variable and then use it in the factory. An exception is made for variables that start with the word 'mock'. It's up to you to guarantee that they will be initialized on time!
What that means is you need to move around the lines of code to make them look like this:
// First the mock functions
const mockMethod1 = jest.fn();
const mockMethod2 = jest.fn();
// Only then your imports & jest.mock calls
import File3 from "./File3";
import File2 from "../Folder2/File2";
jest.mock('../Folder2/File2', () => ({
// ...
}));
The second issue is that you are mocking File2 as if it was exporting method1 and method2, you are not mocking method1 and method2 of the File2 class! Take a look at 4 ways of mocking an ES6 class in jest docs.

Related

How can I mock a class using jest?

How can I mock something to test something like the following codes. I tried to follow this official doc, but still not working for me https://jestjs.io/docs/es6-class-mocks#calling-jestmock-with-the-module-factory-parameter
// somefile.ts
export const myPublish = async (event: any, context: any): Promise<any> => {
const myExportHelper = await ExportHelper.getInstance({
...commonProps,
});
// just some other stuff
// just some other stuff
await myExportHelper.transfer(arg1, arg2);
};
export class ExportHelper {
constructor(
private readonly bucket: string,
private readonly read: AWS.S3,
private readonly write: AWS.S3
) {}
static async getInstance(props: {
param1: string;
}) {
...
...
return new ExportHelper(arg1, arg2, arg3);
};
async transfer(param1, param2) {
...
...
console.log('bla bla bla');
}
}
// testfile.test.ts
import { myPublish, ExportHelper } from '../somefile';
beforeEach(() => {
});
afterEach(() => {
jest.clearAllMocks();
jest.resetAllMocks();
});
describe('myTest', () => {
it('should run successfully', async () => {
// Arrange
const eventMock = {
Records: [
{
...
}
]
}
jest.mock('../somefile');
const mockActualExportHelper = jest.requireActual('../somefile').ExportHelper;
const mockGetInstanceImpl = () => {};
// this says cannot read property instances of undefined
const mockExportHelper = mockActualExportHelper.mock.instances[0];
mockExportHelper.getInstance.mockImplementation(mockGetInstanceImpl);
mockExportHelper.transfer.mockImplementation(mockGetInstanceImpl);
// Act
await myPublish(eventMock, jasmine.any({}));
// Assert
expect(ExportHelper.getInstance).toBeCalled();
expect(ExportHelper.transfer).toBeCalled(); // also not sure if this is valid to use ExportHelper
});
});
I think what you're looking for is not a mock. If you want to spy what functions are called, you will need to use the spyOn. In jest you can do the following:
jest.spyOn(MyClass, 'myMethod');
And you can also mock the implementation to subtitute the default behavior of a method, a generalist example can be like this:
jest.spyOn(MyClass, 'myMethod').mockImplementation(jest.fn());
With that said, I would rewrite the test to spy the methods from ExportHelper and avoid external calls:
import {ExportHelper, myPublish} from '../app';
beforeEach(() => {
});
afterEach(() => {
jest.clearAllMocks();
jest.resetAllMocks();
});
describe('myTest', () => {
it('should run successfully', async () => {
// Arrange
const eventMock = {
Records: [
{
// ...
}
]
}
jest.spyOn(ExportHelper, 'getInstance').mockImplementation(jest.fn());
jest.spyOn(ExportHelper, 'transfer').mockImplementation(jest.fn());
// Act
await myPublish('arg1', 'arg2');
// Assert
expect(ExportHelper.getInstance).toBeCalled();
expect(ExportHelper.transfer).toBeCalled();
});
});
I just replaced this piece of code:
jest.mock('../somefile');
const mockActualExportHelper = jest.requireActual('../somefile').ExportHelper;
const mockGetInstanceImpl = () => {};
// this says cannot read property instances of undefined
const mockExportHelper = mockActualExportHelper.mock.instances[0];
mockExportHelper.getInstance.mockImplementation(mockGetInstanceImpl);
mockExportHelper.transfer.mockImplementation(mockGetInstanceImpl);
with
jest.spyOn(ExportHelper, 'getInstance').mockImplementation(jest.fn());
jest.spyOn(ExportHelper, 'transfer').mockImplementation(jest.fn());
Because jest will track down and watch any of these method's calls and then we can use jest's matchers to test if both of them were called. And the mockImplementation will isolate any further calls to be maded.
One thing that I noticed while reproducing your example, is that the transfer method is not being treated as a method when you get the instance from getInstance and therefore, the tests will not pass. But I think this question is not in the scope of the topic. Dunno if just happens to me.

Jest mock not being used when called from a constant being imported from another file

I have a method which is using a constant defined in another call. This constant in itself makes a service call that I have mocked. When resolving this constant from the other file the mocked function is not being called
//function i am trying in test.ts
export const testableFunction = async() => {
return EXPORTED_CONSTANT
}
//constant being exported from constants.ts
export const EXPORTED_CONSTANT = {
value : service.method()
}
// unit test I have written
it('message trigger for sendInteractiveWhatsapp', async () => {
jest.spyOn(service, 'method').mockImplementation(async () => {
return "some_value";
});
expect(await testableFunction()).toEqual({value: 'some_value'});
});
This test is failing because the mock value being return is undefined. i.e.
{} != {value: 'some_value'}
You can try mocking the constants.ts file at the top of your tests. That way you will be mocking any uses of that module and its contents while your tests run. Following is a sample from jest documentation.
import moduleName, {foo} from '../moduleName';
jest.mock('../moduleName', () => {
return {
__esModule: true,
default: jest.fn(() => 42),
foo: jest.fn(() => 43),
};
});
moduleName(); // Will return 42
foo(); // Will return 43
You can read more about different types of mocks here

Mocking classes in Jest does not call the same method

I'm trying to mock a class that is being imported into my code with require and then testing if a method of that class is getting called.
I've created a sample setup where this issue can be replicated:
// user.js
class User {
getName() {
return "Han Solo"
}
}
module.exports = User
// user-consumer.js
const User = require('./user')
const user = new User()
module.exports.getUserName = () => {
// do things here
return user.getName()
}
// user.test.js
const userConsumer = require('./user-consumer')
const User = require('./user')
jest.mock('./user')
it('should mock', () => {
const user = new User()
jest.spyOn(user, 'getName')
userConsumer.getUserName()
expect(user.getName).toBeCalled()
})
The error I get is as follows:
If I used ES6 syntax this would work as shown on jest's documentation: https://jestjs.io/docs/en/es6-class-mocks
But I unfortunately can't use ES6 on this project as it would require a lot of refactoring.
I also tried mocking the class with the module factory parameter
jest.mock('./user', () => {
return jest.fn(() => {
return {
getName: jest.fn(),
}
})
})
It still doesn't work. When I log console.log(user.getName) in user-consumer.js:5 it does show that the method has been mocked but whatever is called in user.getName() is not the consumer function still returns "Han Solo".
I've also tried it with and without jest.spyOn and it still returns the same error.
Is this just not possible with none ES6 syntax?
The problem is that Jest spies have undocumented behaviour.
Even if prototype method is the same for all instances:
new User().getName === new User().getName
A spy is specific to an instance:
jest.spyOn(new User(), 'getName') !== jest.spyOn(new User(), 'getName')
If a specific instance is unreachable, it's a prototype that needs to be spied:
jest.spyOn(User.prototype, 'getName')
userConsumer.getUserName()
expect(User.prototype.getName).toBeCalled();
A problem with jest.mock isn't specific to ES6 syntax. In order for a spy to be available for assertions and implementation changes, it should be exposed somewhere. Declaring it outside jest.mock factory is not a good solution as it can often result in race condition described in the manual; there will be one in this case too. A more safe approach is to expose a reference as a part of module mock.
It would be more straightforward for ES module because this way class export is kept separately:
import MockedUser, { mockGetName } from './user';
jest.mock('./user', () => {
const mockGetName = jest.fn();
return {
__esModule: true,
mockGetName,
default: jest.fn(() => {
return {
getName: mockGetName
}
})
}
})
...
For CommonJS module with class (function) export, it will be efficiently exposed as class static method:
import MockedUser from './user';
jest.mock('./user', () => {
const mockGetName = jest.fn();
return Object.assign(
jest.fn(() => {
return {
getName: mockGetName
}
}),
{ mockGetName }
})
})
...
MockedUser.mockGetName.mockImplementation(...);
userConsumer.getUserName()
expect(MockedUser.mockGetName).toBeCalled();

jest ReferenceError: Cannot access '' before initialization

I'm getting the error:
ReferenceError: Cannot access 'myMock' before initialization
Even though i respected jest documentation about the hoisting:
A limitation with the factory parameter is that, since calls to jest.mock() are hoisted to the top of the file, it's not possible to first define a variable and then use it in the factory. An exception is made for variables that start with the word 'mock'.
I'm doing this:
import MyClass from './my_class';
import * as anotherClass from './another_class';
const mockMethod1 = jest.fn();
const mockMethod2 = jest.fn();
jest.mock('./my_class', () => {
return {
default: {
staticMethod: jest.fn().mockReturnValue(
{
method1: mockMethod1,
method2: mockMethod2,
})
}
}
});
as you can see both of my variables respect the "standard" but are not hoisted properly.
Am I missing something ?
Obviously it works when I just pass jest.fn() instead of my variables, but i'm not sure how to be able to use these in my test later on.
None of the answers above solved my problem, so here's my solution:
var mockMyMethod: jest.Mock;
jest.mock('some-package', () => ({
myMethod: mockMyMethod
}));
Something about using const before the imports feels weird to me. The thing is: jest.mock is hoisted. To be able to use a variable before it you need to use var, because it is hoisted as well. It doesn't work with let and const because they aren't.
The accepted answer does not handle when you need to spy on the const declaration, as it is defined inside the module factory scope.
For me, the module factory needs to be above any import statement that eventually imports the thing you want to mock.
Here is a code snippet using a nestjs with prisma library.
// app.e2e.spec.ts
import { Test, TestingModule } from '#nestjs/testing';
import { INestApplication } from '#nestjs/common';
import * as request from 'supertest';
import mockPrismaClient from './utils/mockPrismaClient'; // you can assert, spy, etc. on this object in your test suites.
// must define this above the `AppModule` import, otherwise the ReferenceError is raised.
jest.mock('#prisma/client', () => {
return {
PrismaClient: jest.fn().mockImplementation(() => mockPrismaClient),
};
});
import { AppModule } from './../src/app.module'; // somwhere here, the prisma is imported
describe('AppController (e2e)', () => {
let app: INestApplication;
beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
)};
To clarify what Jason Limantoro said, move the const above where the module is imported:
const mockMethod1 = jest.fn(); // Defined here before import.
const mockMethod2 = jest.fn();
import MyClass from './my_class'; // Imported here.
import * as anotherClass from './another_class';
jest.mock('./my_class', () => {
return {
default: {
staticMethod: jest.fn().mockReturnValue(
{
method1: mockMethod1,
method2: mockMethod2,
})
}
}
});
The problem that the documentation addresses is that jest.mock is hoisted but const declaration is not. This results in factory function being evaluated at the time when mocked module is imported and a variable being in temporal dead zone.
If it's necessary to access nested mocked functions, they need to be exposed as a part of export object:
jest.mock('./my_class', () => {
const mockMethod1 = jest.fn();
const mockMethod2 = jest.fn();
return {
__esModule: true,
mockMethod1,
mockMethod2,
default: {
...
This also applies to manual mocks in __mocks__ where variables are accessible inside a mock only.
You should move your mocking above your imports; that could be the source of your issue. Imports are also hoisted, so multiple hoisted entries would be hoisted in order.
jest.mock('./my_class', () => {
const mockMethod = jest.fn()
const default = { staticMethod: jest.fn().mockReturnValue({ method: mockMethod }) };
return { default, mockMethod };
});
import MyClass, { mockMethod } from './my_class'; // will import your mock
import * as anotherClass from './another_class';
However, if you for some reason can't do that, you could use doMock to avoid hoisting behaviour. If this happens on the top of your file, it should be a 1 to 1 change.
const mockMyMethod = jest.fn();
jest.doMock('some-package', () => ({ myMethod: mockMyMethod }));
This solution works for me and it's pretty easy for vuejs+ jest.
Two points to note:
you should declare the absolute path and not '#/js/network/repositories'
the getter helps to defer the instantiation
const mockGetNextStatuses = jest.fn();
const mockUpdatePrintingStatus = jest.fn();
jest.mock('../../../../../../src/js/network/repositories/index.js', () => {
return {
get printing() {
return {
getNextStatuses: mockGetNextStatuses,
updatePrintingStatus: mockUpdatePrintingStatus,
}
}
}
});
or
jest.mock('../../../../../../src/js/network/repositories/index.js', () => ({
printing: {
getNextStatuses: jest.fn(),
updatePrintingStatus: jest.fn()
}
}));
import { printing } from '../../../../../../src/js/network/repositories/index.js';
// and mock the module
printing.getNextStatuses.mockReturnValue(['XX','YY']);
Example of using TypeScript with Jest and mockDebug.js module
jest.mock('debug', () => {
global.mockDebug = jest.fn();
return () => global.mockDebug;
});
// usage
describe('xxx', () => {
test('xxx', () => {
expect(global.mockDebug.mock.calls.toString()).toContain('ccc');
})
});

Javscript Jest test framework: when is a mock unmocked?

I want to mock a module and a function for a specific test. I have the following:
test("my test", () => {
jest.mock("some_module")
some_module.some_function = jest.fn();
...
})
test("another test", () => {
...
});
My question is, when the test is finished, will both mocks be "unset" so that I can use the real implementation for my next test? Or do I have to explicitly remove all mocks myself?
when the test is finished will all of the mocks be "unset"?
Jest tests are sandboxed on a test-file basis, so ordinarily after all tests in that file have been run, all the mocks would be restored.
However, what you are doing there: some_module.some_function = jest.fn(); is not mocking via Jest's mocking mechanism, it is monkey-patching an imported function. This will not be removed by Jest.
You should be doing something like this instead:
import { some_function } from 'some-module-path';
jest.mock('some-module-path');
test('my test', () => {
...
expect(some_function).toHaveBeenCalled(); // e.g.
}
UPDATE:
After the discussion in comments, here is an example of doing a simple monkey-patch safely in Jest, so that it is restored for subsequent tests in the same file:
// foo.js -----------------------------------
export const foo = () => 'real foo';
// bar.js -----------------------------------
import { foo } from './foo';
export const bar = () => foo();
// bar.test.js ------------------------------
import { bar } from './bar';
import * as fooModule from './foo';
describe('with mocked foo', () => {
let originalFoo;
beforeAll(() => {
// patch it!
originalFoo = fooModule.foo;
fooModule.foo = () => 'mocked foo';
});
afterAll(() => {
// put it back again!
fooModule.foo = originalFoo;
});
test('mocked return value from foo()', () => {
expect(bar()).toEqual('mocked foo');
});
});
describe('with real foo', () => {
test('expect real return value from foo()', () => {
expect(bar()).toEqual('real foo');
});
});
UPDATE 2:
Another alternative, you can mock the dependency and use the original implementation temporarily with jest.requireActual:
import { bar } from './bar';
import { foo } from './foo';
jest.mock('./foo');
foo.mockReturnValue('mocked foo');
const fooModule = jest.requireActual('./foo')
test('mocked return value from foo()', () => {
expect(bar()).toEqual('mocked foo');
});
test('real return value from foo()', () => {
foo.mockImplementation(fooModule.foo);
expect(bar()).toEqual('real foo');
});

Categories

Resources