javascript - 在单元测试期间我应该模拟哪些功能
问题描述
我一直在阅读一些文章,并在 Stack Overflow 上发布了关于何时应该模拟函数以及何时不应该模拟函数的帖子,但我有一个案例我不确定该怎么做。
我有一个 UserService 类,它使用依赖注入概念通过其构造函数接收依赖项。
class UserService {
constructor(userRepository) {
this.userRepository = userRepository;
}
async getUserByEmail(userEmail) {
// would perform some validations to check if the value is an e-mail
const user = await this.userRepository.findByEmail(email);
return user;
}
async createUser(userData) {
const isEmailInUse = await this.getUserByEmail(userData.email);
if(isEmailInUse) {
return "error";
}
const user = await this.userRepository.create(userData);
return user;
}
}
我想测试 createUser 方法是否正常工作,对于我的测试,我创建了一个假 userRepository,它基本上是一个带有模拟方法的对象,我将在实例化 UserService 类时使用它
const UserService = require('./UserService.js');
describe("User Service tests", () => {
let userService;
let userRepository;
beforeEach(() => {
userRepository = {
findOne: jest.fn(),
create: jest.fn(),
}
userService = new UserService(userRepository);
});
afterEach(() => {
resetAllMocks();
});
describe("createUser", () => {
it("should be able to create a new user", async () => {
const newUserData = { name: 'User', email: 'user@test.com.br' }
const user = { id: 1, name: 'User', email: 'user@test.com.br' }
userRepository.create.mockResolvedValue(user);
const result = await userService.createUser();
expect(result).toStrictEqual(user);
})
})
})
请注意,在 createUser 方法中,调用了 getUserByEmail 方法,该方法也是 UserService 类的方法,这就是我感到困惑的地方。
我是否应该模拟 getUserByEmail 方法,即使它是我正在测试的类的方法?如果这不是正确的方法,我该怎么办?
解决方案
在这种情况下,您几乎总是不希望模拟您应该测试的部分UserService
。为了说明原因,请考虑以下两个测试:
findByEmail
为repo 对象提供测试双重实现:it("throws an error if the user already exists", async () => { const email = "foo@bar.baz"; const user = { email, name: "Foo Barrington" }; const service = new UserService({ findByEmail: (_email) => Promise.resolve(_email === email ? user : null), }); await expect(service.createUser(user)).rejects.toThrow("User already exists"); });
存根服务自己的
getUserByEmail
方法:it("throws an error if the user already exists", async () => { const email = "foo@bar.baz"; const user = { email, name: "Foo Barrington" }; const service = new UserService({}); service.getUserByEmail = (_email) => Promise.resolve(_email === email ? user : null); await expect(service.createUser(user)).rejects.toThrow("User already exists"); });
对于您当前的实现,两者都可以通过。但是让我们想想事情可能会如何变化。
想象一下,我们需要在某个时候丰富用户模型getUserByEmail
提供的内容:
async getUserByEmail(userEmail) {
const user = await this.userRepository.findByEmail(userEmail);
user.moreStuff = await.this.userRepository.getSomething(user.id);
return user;
}
显然我们不需要这些额外的数据来知道用户是否存在,所以我们把基本的用户对象检索分解出来:
async getUserByEmail(userEmail) {
const user = await this._getUser(userEmail);
user.moreStuff = await.this.userRepository.getSomething(user.id);
return user;
}
async createUser(userData) {
if (await this._getUser(userData.email)) {
throw new Error("User already exists");
}
return this.userRepository.create(userData);
}
async _getUser(userEmail) {
return this.userRepository.findByEmail(userEmail);
}
如果我们使用测试 1,我们根本不需要更改它- 我们仍在使用findByEmail
repo,内部实现已更改的事实对我们的测试是不透明的。但是对于测试 2,即使代码仍然执行相同的操作,它现在也失败了。这是一个假阴性;该功能有效,但测试失败。
实际上,您可以在_getUser
新功能之前应用该重构,从而使需求变得如此清晰;createUser
使用getUserByEmail
直接反映了意外重复的事实this.userRepository.findByEmail(email)
——它们有不同的改变理由。
或者想象我们做了一些破坏性的改变 getUserByEmail
。让我们模拟一个富集问题,例如:
async getUserByEmail(userEmail) {
const user = await this.userRepository.findByEmail(userEmail);
throw new Error("lol whoops!");
return user;
}
如果我们使用测试 1,我们的测试createUser
也会失败,但这是正确的结果!实现已损坏,无法创建用户。对于测试 2,我们有误报;测试通过,但功能不起作用。
在这种情况下,您可以说最好看到only getUserByEmail
失败了,因为这就是问题所在,但我认为当您查看代码时这会非常令人困惑:"createUser
也调用该方法,但测试说没关系……”。
推荐阅读
- python - 基于 P4 语言的计量监控错误
- css-transitions - 使用scrollmagic自动滚动
- android - Android:我们不小心将我们的应用程序投入生产,需要将其移回内部测试,我们该怎么做?
- sql - if 语句中的 SQL 触发器
- database - 使用 Gorm 查询多列
- algorithm - 连通图的 DFS
- discord - 由于没有端口转发,并且您不知道我的 IP 地址,托管 Discord Bot 是如何工作的?
- xcode - 在 Xcode 中运行 AR App 模板时出现错误
- python - 通过 JSON 问题在 Python tradingview-binance webhook 中将要购买/出售的硬币数量设置为“所有硬币”
- python - 当被测小部件必须可见才能工作时,PyQt5 下的 QtTest 失败