Javascript 在单元测试期间,我应该模拟哪些函数
我已经阅读了一些关于Stack Overflow的文章,并在这里发布了关于何时应该模拟函数以及何时不应该模拟函数的帖子,但我有一个案例,我不确定该怎么做 我有一个UserService类,它使用依赖项注入概念通过其构造函数接收依赖项Javascript 在单元测试期间,我应该模拟哪些函数,javascript,unit-testing,jestjs,mocking,Javascript,Unit Testing,Jestjs,Mocking,我已经阅读了一些关于Stack Overflow的文章,并在这里发布了关于何时应该模拟函数以及何时不应该模拟函数的帖子,但我有一个案例,我不确定该怎么做 我有一个UserService类,它使用依赖项注入概念通过其构造函数接收依赖项 class UserService { constructor(userRepository) { this.userRepository = userRepository; } async getUserByEmail(userEmail) {
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
提供双重测试实现:
it(“如果用户已经存在,则抛出错误”),async()=>{
常量电子邮件=”foo@bar.baz";
const user={电子邮件,名称:“Foo Barrington”};
const service=新用户服务({
findByEmail:(\u email)=>Promise.resolve(\u email==email?用户:null),
});
wait expect(service.createUser(user)).rejects.toThrow(“用户已存在”);
});
getUserByEmail
方法:
it(“如果用户已经存在,则抛出错误”),async()=>{
常量电子邮件=”foo@bar.baz";
const user={电子邮件,名称:“Foo Barrington”};
const service=new UserService({});
service.getUserByEmail=(\u email)=>Promise.resolve(\u email==email?用户:null);
wait expect(service.createUser(user)).rejects.toThrow(“用户已存在”);
});
设想一下,我们需要在某个时候丰富用户模型
getUserByEmail
提供的功能:
异步getUserByEmail(userEmail){
const user=wait this.userRepository.findByEmail(userEmail);
user.moreStuff=await.this.userRepository.getSomething(user.id);
返回用户;
}
显然,我们不需要这些额外的数据,只是为了知道用户是否存在,所以我们考虑了基本的用户对象检索:
异步getUserByEmail(userEmail){
const user=等待此消息。\u getUser(userEmail);
user.moreStuff=await.this.userRepository.getSomething(user.id);
返回用户;
}
异步createUser(用户数据){
如果(等待此消息。\u getUser(userData.email)){
抛出新错误(“用户已存在”);
}
返回this.userRepository.create(userData);
}
异步\u getUser(用户电子邮件){
返回此.userRepository.findByEmail(userEmail);
}
如果我们使用测试1,我们根本不需要更改它——我们仍然在使用回购协议上的findByEmail
,内部实现已经更改的事实对我们的测试来说是不透明的。但是在测试2中,即使代码仍然执行相同的操作,也会失败。这是一个假阴性;功能正常,但测试失败
事实上,您可以在一个新的特性使需求变得如此清晰之前,应用重构,提取\u getUser
;createUser
使用getUserByEmail
这一事实直接反映了此.userRepository.findByEmail(email)
的意外重复-它们有不同的更改原因
或者想象一下,我们做了一些改变,破坏了
getUserByEmail
。让我们模拟一个浓缩问题,例如:
异步getUserByEmail(userEmail){
const user=wait this.userRepository.findByEmail(userEmail);
抛出新错误(“哈哈哈!”);
返回用户;
}
如果我们使用测试1,我们对createUser
的测试也会失败,但这是正确的结果!实现已中断,无法创建用户。对于测试2,我们有一个假阳性;测试通过,但功能不起作用
在这种情况下,您可以说最好看到只有
getUserByEmail
失败,因为这就是问题所在,但我认为,当您查看代码时,这将非常令人困惑:“createUser
也调用该方法,但测试表明它很好……”这个问题是基于观点的,但是我想说的是,只有模拟存储库才能使整个类更容易测试。只有一件事需要模拟,通过这种方式,您可以检查您的类方法是否协同工作以获得预期的结果(当您尝试创建现有用户时返回“error”
)@blex我担心的是,如果getUserByEmail方法有一些bug,createUser测试可能会失败,即使问题不在函数本身,它始终取决于具体情况。你可以考虑他们不同的单位,单独测试,或者考虑它们,一起测试。由于您模拟了依赖项,这减少了移动部件的数量,因此即使问题不在函数本身,失败的风险也会降低。不管怎样,你可以监视一个方法来确保一个调用满足你的期望