Jest.js 在单元测试期间应该模拟哪些函数

tzdcorbm  于 2023-05-27  发布在  Jest
关注(0)|答案(2)|浏览(230)

我一直在阅读一些文章,以及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方法是我正在测试的类的一个方法,我是否应该模拟它?如果这不是正确的方法,我该怎么办?

bprjcwpo

bprjcwpo1#

你应该总是倾向于not来模拟你应该测试的部分,在这个例子中是UserService。为了说明原因,考虑以下两个测试:
1.在repo对象上提供findByEmail的测试double实现:

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");
});

1.删除服务自己的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);
}

如果我们使用的是test 1,我们 * 根本不需要修改它 * -我们仍然在repo上使用findByEmail,内部实现已经改变的事实对我们的测试是不透明的。但是在测试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,我们有假阴性;测试通过,但功能不起作用。
在这种情况下,你可以说最好看到 onlygetUserByEmail失败,因为这就是问题所在,但我认为当你查看代码时,这会非常令人困惑:* ”createUser也调用了该方法,但测试表明它很好..."*。

ars1skjm

ars1skjm2#

您不应该模仿这些函数中的任何一个,因为它创建用户并从数据库阅读数据。如果你嘲笑他们,那测试还有什么意义。换句话说,你不知道你的应用程序是否正确地使用数据库。无论如何,我会模仿一些函数,比如发送电子邮件的函数等等。不要嘲笑作为应用程序核心的函数。您应该有一个用于测试的数据库和另一个用于生产的数据库。

相关问题