admin管理员组文章数量:1241129
service.js
this file defines a Service class with a constructor that initializes a value property and a getValue method that returns this value. The file exports an instance of the Service class.
class Service {
constructor() {
this.value = 'A';
}
getValue() {
return this.value;
}
}
export default new Service();
main.js
this file imports a service and defines a run function that logs a message with a value obtained from the service.
import service from './service';
export function run() {
const value = service.getValue();
console.log('Executing service with value ' + value);
}
Main.test.js
import { jest } from '@jest/globals';
import { run } from './main';
jest.mock('./service', () => {
return jest.fn().mockImplementation(() => {
return {
getValue: jest.fn().mockReturnValue('mocked_value'),
};
});
});
describe('test run', () => {
it('should log the correct message', () => {
console.log = jest.fn();
run();
expect(console.log).toHaveBeenCalledWith('Executing service with value mocked_value');
});
});
What happens?
Expected: "Executing service with value mocked_value"
Received: "Executing service with value A"
Can anyone help getting the mock work? Thanks.
service.js
this file defines a Service class with a constructor that initializes a value property and a getValue method that returns this value. The file exports an instance of the Service class.
class Service {
constructor() {
this.value = 'A';
}
getValue() {
return this.value;
}
}
export default new Service();
main.js
this file imports a service and defines a run function that logs a message with a value obtained from the service.
import service from './service';
export function run() {
const value = service.getValue();
console.log('Executing service with value ' + value);
}
Main.test.js
import { jest } from '@jest/globals';
import { run } from './main';
jest.mock('./service', () => {
return jest.fn().mockImplementation(() => {
return {
getValue: jest.fn().mockReturnValue('mocked_value'),
};
});
});
describe('test run', () => {
it('should log the correct message', () => {
console.log = jest.fn();
run();
expect(console.log).toHaveBeenCalledWith('Executing service with value mocked_value');
});
});
What happens?
Expected: "Executing service with value mocked_value"
Received: "Executing service with value A"
Can anyone help getting the mock work? Thanks.
Share Improve this question edited 2 days ago Alex asked 2 days ago AlexAlex 312 silver badges7 bronze badges 8- What you do could work. This depends on your setup, which isn't shown. Please, provide a way to reproduce – Estus Flask Commented 2 days ago
- github/aexel90/jest_mock – Alex Commented 2 days ago
- codesandbox.io/p/github/aexel90/jest_mock/main – Alex Commented 2 days ago
- You need to use babel transform in order to be able to mock modules in unrestricted way. Currently you're using native esm, see jestjs.io/docs/ecmascript-modules – Estus Flask Commented 2 days ago
- What does it mean - unrestricted way? Do you have a working example? Or could you fork the repository and propose a solution. Much appreciated. – Alex Commented 2 days ago
3 Answers
Reset to default 1The problem is that Jest project isn't configured correctly to mock modules, currently it uses native ES modules, and jest.mock
does nothing. Module mocking is limited with native ESM and only works with dynamic imports and jest.unstable_mockModule
.
The current edition of the question contains incorrect mock, the previously used one should be used:
jest.mock('./service', () => {
return {
__esModule: true,
default: {
getValue: jest.fn().mockReturnValue('mocked_value'),
},
};
});
Jest configuration needs to contain Babel transform, transform
option needs to be removed in order to use default value.
The project needs to contain correct Babel configuration, e.g. babel.config.json:
{
"presets": ["@babel/preset-env"]
}
This also requires @babel/preset-env
package to be installed.
Either native ESMs need to be disabled by removing "type": "module"
in package.json, or --experimental-require-module
option needs to be used to run Jest:
node --experimental-require-module node_modules/jest/bin/jest.js
Did you consider the possibility to switch to Vitest? It provides a Jest compatible API and works as a drop-in replacement.
I know this doesn't answer your question directly, but it might be a good alternative to consider.
Your test case written for Vitest would look like this:
import { vi, describe, it, expect } from 'vitest';
import { run } from './main';
import Service from './service';
describe('test run', () => {
it('should log the correct message', () => {
const mock = vi.spyOn(Service, 'getValue');
mock.mockReturnValue('mocked_value');
console.log = vi.fn();
run();
expect(console.log).toHaveBeenCalledWith('Executing service with value mocked_value');
});
});
For this no additional configuration for Vitest is required.
In Jest I don't see real progress on supporting ESM out of the box during a really long time. And as you can see in the comments it requires quite some configuration to bring it to work with Jest.
I also had a quick try setting it up in Jest, but I didn't get it to work quickly. It's possible in Jest, but it's not as easy as it should be.
At the time of writing I think Jest feels to me like a dead end.
Issue
The issue is due to the fact that the mock for ./service
is not affecting the service
instance that was imported into the main.js
file. In Jest, jest.mock()
works by replacing the module during the import phase, but since the service
module is imported and instantiated as a singleton (new Service()
), it's not properly mocked in my initial try.
Here's a breakdown of the issue:
- In
service.js
, exporting a singleton instance of theService
class:export default new Service();
- In
main.js
, theservice
import is directly using that singleton:import service from './service';
- In
Main.test.js
, trying to mockgetValue()
usingjest.mock()
, but becauseservice.js
is already instantiated before the test runs, the mock does not override the singleton instance properly.
Solution with jest.mock
https://github/aexel90/jest_mock/tree/babel
npm install --save-dev babel-jest
npm install @babel/preset-env --save-dev
babel.config.json
{
"presets": [
"@babel/preset-env"
]
}
package.json
{
...
"scripts": {
"test": "node --experimental-require-module node_modules/jest/bin/jest.js"
},
...
}
import { jest } from '@jest/globals';
import { run } from './main';
jest.mock('./service', () => {
return {
__esModule: true,
default: {
getValue: jest.fn().mockReturnValue('mocked_value'),
},
};
});
describe('test run', () => {
it('should log the correct message', () => {
console.log = jest.fn();
run();
expect(console.log).toHaveBeenCalledWith('Executing service with value mocked_value');
});
});
Solution with jest.unstable_mockModule
https://github/aexel90/jest_mock
import { jest } from '@jest/globals';
jest.unstable_mockModule('./service', async () => {
class MockedService {
getValue() {
return 'mocked_value';
}
}
return {
default: new MockedService(),
};
});
describe('test run', () => {
it('should log the correct message', async () => {
const { run } = await import('./main');
console.log = jest.fn();
run();
expect(console.log).toHaveBeenCalledWith('Executing service with value mocked_value');
});
});
The unstable_mockModule
function is called with the path to the module to be mocked and an asynchronous function that returns the mocked implementation.
In this case, the mocked implementation is a class MockedService
with a getValue
method that returns the string mocked_value
.
With await import(...)
the instantiation happens after the mock is already active.
An instance of this mocked class is then returned as the default export of the ./service
module => the singleton could be mocked
Solution with jest.unstable_mockModule and changing the singleton during test execution
import { jest } from '@jest/globals';
jest.unstable_mockModule('./service', async () => {
class MockedService {
getValue() {
return 'default_value';
}
}
return {
default: new MockedService(),
};
});
const { run } = await import('./main');
const { default: service } = await import('./service');
describe('test run', () => {
it('should log the correct message with default_value', async () => {
console.log = jest.fn();
run();
expect(console.log).toHaveBeenCalledWith('Executing service with value default_value');
});
it('should log the correct message with mocked_value', async () => {
service.constructor.prototype.getValue = function (getValue) {
return 'mocked_value';
};
console.log = jest.fn();
run();
expect(console.log).toHaveBeenCalledWith('Executing service with value mocked_value');
});
});
本文标签: javascriptJest mock singleton instanceStack Overflow
版权声明:本文标题:javascript - Jest mock singleton instance - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1740103218a2224809.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论