admin管理员组文章数量:1285887
Here's the full minimal repro
Given the following app:
src/food.js
const Food = {
carbs: "rice",
veg: "green beans",
type: "dinner"
};
export default Food;
src/food.js
import Food from "./food";
function formatMeal() {
const { carbs, veg, type } = Food;
if (type === "dinner") {
return `Good evening. Dinner is ${veg} and ${carbs}. Yum!`;
} else if (type === "breakfast") {
return `Good morning. Breakfast is ${veg} and ${carbs}. Yum!`;
} else {
return "No soup for you!";
}
}
export default function getMeal() {
const meal = formatMeal();
return meal;
}
I have the following test:
_tests_/meal_test.js
import getMeal from "../src/meal";
describe("meal tests", () => {
beforeEach(() => {
jest.resetModules();
});
it("should print dinner", () => {
expect(getMeal()).toBe(
"Good evening. Dinner is green beans and rice. Yum!"
);
});
it("should print breakfast (mocked)", () => {
jest.doMock("../src/food", () => ({
type: "breakfast",
veg: "avocado",
carbs: "toast"
}));
// prints out the newly mocked food!
console.log(require("../src/food"));
// ...but we didn't mock it in time, so this fails!
expect(getMeal()).toBe("Good morning. Breakfast is avocado and toast. Yum!");
});
});
How do I correctly mock out Food
per test? In other words, I only want to apply the mock for the "should print breakfast (mocked)"
test case.
I would also like to not change the application source code ideally (although maybe having Food
be a function that returns an object instead would be acceptable - still can't get that to work either)
Things I've tried already:
- thread the
Food
object around throughgetMeal
+ use dependency injection intoformatMeal
- (the whole point of this approach IRL is that we don't want to thread
Food
around the whole app)
- (the whole point of this approach IRL is that we don't want to thread
- manual mock +
jest.mock()
- it's possible the answer is here somewhere, but it's tough to control the value here and reset it per test due to import time weirdness- Using
jest.mock()
at the top would override it for every test case, and I can't work out how to then change or reset the value ofFood
per test.
- Using
Here's the full minimal repro
Given the following app:
src/food.js
const Food = {
carbs: "rice",
veg: "green beans",
type: "dinner"
};
export default Food;
src/food.js
import Food from "./food";
function formatMeal() {
const { carbs, veg, type } = Food;
if (type === "dinner") {
return `Good evening. Dinner is ${veg} and ${carbs}. Yum!`;
} else if (type === "breakfast") {
return `Good morning. Breakfast is ${veg} and ${carbs}. Yum!`;
} else {
return "No soup for you!";
}
}
export default function getMeal() {
const meal = formatMeal();
return meal;
}
I have the following test:
_tests_/meal_test.js
import getMeal from "../src/meal";
describe("meal tests", () => {
beforeEach(() => {
jest.resetModules();
});
it("should print dinner", () => {
expect(getMeal()).toBe(
"Good evening. Dinner is green beans and rice. Yum!"
);
});
it("should print breakfast (mocked)", () => {
jest.doMock("../src/food", () => ({
type: "breakfast",
veg: "avocado",
carbs: "toast"
}));
// prints out the newly mocked food!
console.log(require("../src/food"));
// ...but we didn't mock it in time, so this fails!
expect(getMeal()).toBe("Good morning. Breakfast is avocado and toast. Yum!");
});
});
How do I correctly mock out Food
per test? In other words, I only want to apply the mock for the "should print breakfast (mocked)"
test case.
I would also like to not change the application source code ideally (although maybe having Food
be a function that returns an object instead would be acceptable - still can't get that to work either)
Things I've tried already:
- thread the
Food
object around throughgetMeal
+ use dependency injection intoformatMeal
- (the whole point of this approach IRL is that we don't want to thread
Food
around the whole app)
- (the whole point of this approach IRL is that we don't want to thread
- manual mock +
jest.mock()
- it's possible the answer is here somewhere, but it's tough to control the value here and reset it per test due to import time weirdness- Using
jest.mock()
at the top would override it for every test case, and I can't work out how to then change or reset the value ofFood
per test.
- Using
3 Answers
Reset to default 19 +100Short answer
Use require
to grab a fresh module in every test function after setting up mocks.
it("should print breakfast (mocked)", () => {
jest.doMock(...);
const getMeal = require("../src/meal").default;
...
});
or
Turn Food
into a function and put a call to jest.mock
into module scope.
import getMeal from "../src/meal";
import food from "../src/food";
jest.mock("../src/food");
food.mockReturnValue({ ... });
...
Long answer
There is a snippet in Jest manual that reads:
Note: In order to mock properly, Jest needs jest.mock('moduleName') to be in the same scope as the require/import statement.
The same manual also states:
If you're using ES module imports then you'll normally be inclined to put your import statements at the top of the test file. But often you need to instruct Jest to use a mock before modules use it. For this reason, Jest will automatically hoist jest.mock calls to the top of the module (before any imports).
ES6 imports are resolved in the module scope before any of the test functions execute. Thus for mocks to be applied, they need to be declared outside of test functions and before any modules are imported. Jest's Babel plugin will "hoist" jest.mock
statements to the beginning of the file so they are executed before any imports take place. Note that jest.doMock
is deliberately not hoisted.
One can study the generated code by taking a peek into Jest's cache directory (run jest --showConfig
to learn the location).
The food
module in the example is difficult to mock because it is an object literal and not a function. The easiest way is to force a reload of the module every time the value needs to be changed.
Option 1a: Do not use ES6 modules from tests
ES6 import statements must be module scoped, however the "good old" require
has no such limitation and can be called from the scope of a test method.
describe("meal tests", () => {
beforeEach(() => {
jest.resetModules();
});
it("should print dinner", () => {
const getMeal = require("../src/meal").default;
expect(getMeal()).toBe(
"Good evening. Dinner is green beans and rice. Yum!"
);
});
it("should print breakfast (mocked)", () => {
jest.doMock("../src/food", () => ({
type: "breakfast",
veg: "avocado",
carbs: "toast"
}));
const getMeal = require("../src/meal").default;
// ...this works now
expect(getMeal()).toBe("Good morning. Breakfast is avocado and toast. Yum!");
});
});
Option 1b: Reload module on every invocation
One can also wrap the function under test.
Instead of
import getMeal from "../src/meal";
use
const getMeal = () => require("../src/meal").default();
Option 2: Register the mock and call through to real functions by default
If the food
module exposed a function and not a literal, it could be mocked. The mock instance is mutable and can be changed from test to test.
src/food.js
const Food = {
carbs: "rice",
veg: "green beans",
type: "dinner"
};
export default function() { return Food; }
src/meal.js
import getFood from "./food";
function formatMeal() {
const { carbs, veg, type } = getFood();
if (type === "dinner") {
return `Good evening. Dinner is ${veg} and ${carbs}. Yum!`;
} else if (type === "breakfast") {
return `Good morning. Breakfast is ${veg} and ${carbs}. Yum!`;
} else {
return "No soup for you!";
}
}
export default function getMeal() {
const meal = formatMeal();
return meal;
}
__tests__/meal_test.js
import getMeal from "../src/meal";
import food from "../src/food";
jest.mock("../src/food");
const realFood = jest.requireActual("../src/food").default;
food.mockImplementation(realFood);
describe("meal tests", () => {
beforeEach(() => {
jest.resetModules();
});
it("should print dinner", () => {
expect(getMeal()).toBe(
"Good evening. Dinner is green beans and rice. Yum!"
);
});
it("should print breakfast (mocked)", () => {
food.mockReturnValueOnce({
type: "breakfast",
veg: "avocado",
carbs: "toast"
});
// ...this works now
expect(getMeal()).toBe("Good morning. Breakfast is avocado and toast. Yum!");
});
});
Of-course there are other options like splitting the test into two modules where one file sets up a mock and the other one uses a real module or returning a mutable object in place of a default export for the food
module so it can be modified by each test and then manually reset in beforeEach
.
@anttix answer is best, but here is another angle that might be useful in other scenarios.
babel-plugin-rewire allows import Food from "./food";
to be over-ridden by the test.
First, yarn add babel-plugin-rewire
babel.config.js
const presets = [
[
"@babel/env",
{
targets: {
node: 'current',
},
},
],
];
const plugins = [
"babel-plugin-rewire"
];
module.exports = { presets, plugins };
meal_test.js
import getMeal from "../src/meal";
import Food from "../src/food";
import { __RewireAPI__ as RewireAPI } from "../src/meal";
describe("meal tests", () => {
// beforeEach(() => {
// jest.resetModules();
// });
afterEach(() => {
RewireAPI.__Rewire__('Food', Food)
});
it("should print dinner", () => {
expect(getMeal()).toBe(
"Good evening. Dinner is green beans and rice. Yum!"
);
});
it("should print breakfast (mocked)", () => {
const mockFood = {
type: "breakfast",
veg: "avocado",
carbs: "toast"
};
RewireAPI.__Rewire__('Food', mockFood)
expect(getMeal()).toBe("Good morning. Breakfast is avocado and toast. Yum!");
});
it("should print dinner #2", () => {
expect(getMeal()).toBe(
"Good evening. Dinner is green beans and rice. Yum!"
);
});
});
2024 Dec
I know there are a lot of really good answers on here. But I found this implementation to be the fastest to setup and easy to manipulate the outcome for multiple tests.
Sudo Type & Class
This would be the example of real classes, services, and types.
type User = { id: string; name: string }
interface Service {
getUser(id: string): Promise<User | null>
}
class Class {
constructor(private service: Service) {}
async getUser(id: string): Promise<User | null> {
return await this.service.getUser(id)
}
}
Mock Service
- Success
const mockService: Service = {
getUser: jest.fn((id: string) => Promise.resolve({ id: id, name: "Jaz" })),
}
- Fail
const mockService: Service = {
getUser: jest.fn((id: string) => Promise.reject(new Error())),
}
Test
it("should return a user", async () => {
const userPromise = new Class(mockService).getUser("123")
expect(userPromise).resolves.toEqual({ id: "123", name: "Jaz" })
})
The goal of this would be to test the Class
methods, not the Service
layer. With this setup you could easily have many mock services that return anything you want.
Hopefully that is helpful
to someone. :)
本文标签: javascriptHow do we mock out dependencies with Jest per testStack Overflow
版权声明:本文标题:javascript - How do we mock out dependencies with Jest per test? - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1738436633a2086715.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论