在单元测试期间我应该模拟哪些功能

2024-03-14

我一直在阅读 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: '[email protected] /cdn-cgi/l/email-protection' }
        const user = { id: 1, name: 'User', email: '[email protected] /cdn-cgi/l/email-protection' }

        userRepository.create.mockResolvedValue(user);

        const result = await userService.createUser();

        expect(result).toStrictEqual(user);
    })
})

})

请注意,在 createUser 方法中,有一个对 getUserByEmail 方法的调用,该方法也是 UserService 类的方法,这就是我感到困惑的地方。

我是否应该模拟 getUserByEmail 方法,即使它是我正在测试的类的方法?如果这不是正确的方法,我该怎么办?


你应该几乎总是更喜欢not在本例中,模拟您应该测试的部分内容UserService。为了说明原因,请考虑以下两个测试:

  1. 提供测试双重实现findByEmail在回购对象上:

    it("throws an error if the user already exists", async () => {
        const email = "[email protected] /cdn-cgi/l/email-protection";
        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");
    });
    
  2. 删除服务自己的服务getUserByEmail method:

    it("throws an error if the user already exists", async () => {
        const email = "[email protected] /cdn-cgi/l/email-protection";
        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");
    });
    

对于您当前的实现,两者都很好。但让我们考虑一下事情可能会发生怎样的变化。


想象一下我们需要enrich用户模型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在回购协议中,内部实现发生变化的事实对于我们的测试来说是不透明的。但对于测试 2,即使代码仍然执行相同的操作,现在还是失败了。这是一个假阳性;功能有效,但测试失败。

事实上,您可以应用该重构,提取_getUser,在新功能明确需求之前;事实是createUser uses getUserByEmail直接反映偶然重复this.userRepository.findByEmail(email)- 他们有不同的改变理由。


或者想象我们做出一些改变breaks getUserByEmail。让我们模拟一个丰富的问题​​,例如:

async getUserByEmail(userEmail) {
    const user = await this.userRepository.findByEmail(userEmail);
    throw new Error("lol whoops!");
    return user;
}

如果我们使用测试 1,我们的测试createUser也失败了,但就是这样correct结果!实现已损坏,无法创建用户。通过测试 2,我们有一个假阴性;测试通过,但功能不起作用。

在这种情况下,你可以说最好看看only getUserByEmail失败了,因为这就是问题所在,但我认为当您查看代码时,这会非常令人困惑:"createUser也调用该方法,但测试表明它没问题......”.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在单元测试期间我应该模拟哪些功能 的相关文章

随机推荐

  • cdata-section-elements 不工作

    我试图通过设置全局参数在通过 XSLT 使用 Saxon HE v9 7 0 14 生成的 xml 文件中设置密码 密码可以包含任何字符 因此需要将其放在CDATA部分 我试图通过设置来实现这一点cdata section elements
  • 删除表格单元格的边框

    我知道这是一个愚蠢的问题 但我似乎完全忘记了该怎么做 我有一个 HTMLtable我想删除所有单元格周围的所有边框 以便整个表格周围只有一个边框 我的代码如下所示 table border 1 width 500 tr th h1 Your
  • Silverlight 3 替代 FileVersionInfo.GetVersionInfo

    在 Silverlight 3 0 应用程序中 我想使用程序集文件版本 http msdn microsoft com en us library system reflection assemblyfileversionattribute
  • 从 History api 接收的步骤数据与 google fit 不匹配

    我希望在我的应用程序中计算 google fit 步数 因为我使用的是 google 提供的 History api 我发现从历史 API 接收的步骤与 google fit 不匹配 即使我使用了 google 提供的相同代码 下面是我的代
  • 创建一个包含 JSONObject Android 中所有键的数组

    您好 我想创建一个 JSONObject 中所有键的数组 我的理解 如果我错了 请纠正我 是我需要将 JSONObject 转换为 Map 然后从中创建一个数组 有人知道如何做到这一点吗 无需转换JSONObject到 Map 然后创建一个
  • Node exceljs读取文件

    所以根据官方文档我应该能够使用以下方式读取Excel文档 read from a file var workbook new Excel Workbook workbook xlsx readFile filename then funct
  • ggplot2 +facet_:某些方面的反转轴?

    我想将三个子图组合成一个图 而分面将是一种自然的方法 然而 使用反转的 x 轴 这些子图之一会更容易 更自然地阅读 而我想不理会其他子图 有没有办法使用facet grid 或facet wrap 来完成此任务 我考虑过的另一种选择是 gr
  • 如何在flutter webview中打开应用程序链接?

    在 Flutter 中 我使用 flutter网页视图插件 https pub dartlang org packages flutter webview plugin启动一个 url 例如 flutterWebviewPlugin lau
  • Sizzle 和 document.querySelectorAll 有什么区别

    据我所知 嘶嘶声和querySelector querySelectorAll是 CSS 选择器 那么 加载 Sizzle 和执行以下操作有什么区别 Sizzle my CSS query and document querySelecto
  • 我如何在 Capybara 中测试页面是否*未*重新加载(JavaScript onClick 拦截已起作用)?

    我用的是水豚 黄瓜和恶作剧 我正在测试附加到表单提交按钮的 JavaScript 函数 该函数旨在捕获提交事件并阻止它 在后台执行 AJAX 请求 使用和不使用 AJAX 页面最终看起来都是一样的 但 AJAX 方法要快得多 并且不会中断浏
  • 开始一项新活动

    我正在使用 Xamarin 我想启动一个名为 AutoLinkActivity 的新活动 这是我的代码 using System using Android App using Android Content using Android R
  • Java 示例代码示例 youtube data api v3 和授权方法作为 api 密钥

    我是 google api 的新手 为 YouTube 频道创建项目 我已经为该项目创建了 api 并生成了 api 密钥 在项目上启用 Youtube api 我到处都能获得 OAUTH 授权的代码示例 但无法找到任何用于使用 api K
  • 平衡设计原则:单元测试

    我正在编写 Bananagrams 的模拟 目前 我有一个GameMaster维护公共片段集合的类 这deal Player 方法向该玩家分发一定数量的棋子 我想为此编写单元测试 然而 此时我没有吸气剂 因此无法检查对象的状态 为什么不添加
  • “lxd”与 lxc/docker 有何不同?

    问题 lxd 如何在容器内提供完整的操作系统功能 而不仅仅是单个进程 它与 lxc docker 包装器有什么不同 是不是类似于用docker supervisor wrapper脚本启动一个容器 在一个容器中包含多个进程 换句话说 我可以
  • 使用加密密码连接到 gmail(使用 imap 和 javamail)

    我正在尝试使用一个简单的java程序连接到gmail 像这个 https harikrishnan83 wordpress com 2009 01 24 access gmail with imap using java mail api
  • 如何使用带有角度的离子框架制作apk文件

    我在本地目录中创建了简单的应用程序 该应用程序名称是 Ionic Chat master 我怎样才能把它变成apk文件 我已经使用 git 尝试了以下命令 但对我不起作用 npm install g cordova cordova buil
  • 为什么 C# 构造函数行为与 Java 不同,反之亦然?

    给定这个 Java 代码 输出0 and 4 class A A print void print System out println A class B extends A int i Math round 3 5f public st
  • 为什么只有一个核心承担全部负载,如何让其他29个核心承担负载?

    我正在尝试将 Spark 处理的数据推送到 C 的 3 节点集群 我正在向 Cassandra 推送 2 亿条记录 但它失败了 错误如下 下面是我的 Spark 集群配置 Nodes 12 vCores Total 112 Total me
  • 如何使用 CSS 或 JS 使图像变暗而不影响透明度?

    到处建议的调暗图像的正常方法是更改 其不透明度属性并在其下方显示黑色的东西 但是 我的图像具有透明度并且位于白色背景上 所以我想将背景保持在图像白色的透明部分下 只使有颜色的像素变暗 这可以在 CSS 最好 或 JS 中完成吗 编辑 示例图
  • 在单元测试期间我应该模拟哪些功能

    我一直在阅读 Stack Overflow 上的一些文章和帖子 了解何时应该模拟函数 何时不应该模拟函数 但我遇到了一个情况 我不确定该怎么做 我有一个 UserService 类 它使用依赖项注入概念通过其构造函数接收依赖项 class