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

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(使用前将#替换为@)

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

  • JS 在两个或多个点之间画线

    我需要在两个标签或标签之间画一条线 Example http caitriona github io connect the dots http caitriona github io connect the dots 我需要做这样的事情
  • 使用 Gmail 上下文小工具访问附件

    我想将电子邮件及其附件从 Gmail Google Apps 保存到另一个数据库以实现类似 CRM 的功能 然而 根据docs http code google com apis gmail gadgets contextual 提取器无法
  • 检测对给定 JavaScript 事件的支持?

    我有兴趣使用 JavaScript hashchange 事件来监视 URL 片段标识符的更改 我知道非常简单的历史 http code google com p reallysimplehistory 以及用于此目的的 jQuery 插件
  • ES6 类文字中的 IIFE

    在 ES5 中我们都可以这样做 myClass prototype myMethod function return function 我可以对 ES6 类文字执行同样的操作吗 不 至少现在还没有 ES6 类仅支持声明方法 因此任何不直接为
  • 光标:IE 8 和 9 中的自动行为

    我想要的是为整个正文标记指定cursor pointer 这样页面的背景是可点击的 但我也希望页面的其余部分像以前一样工作 所以我尝试为div设置cursor auto 其中包含这一页 在 FF Chrome 和 safari 中 它工作得
  • TypeError:cli.init 不是 React Native 的函数

    在 MacBook Air M1 芯片中运行 npx react native init appName 时 TypeError cli init is not a function at run opt homebrew lib node
  • Chrome 跨域 PATCH 请求不起作用

    我有一个带有 REST Api 的网站 现在我正在创建一个浏览器扩展 它将从某些页面收集数据并将它们发送回 REST Api 因为我希望我的扩展能够与 Firefox 和 Chrome 兼容 并且易于维护 所以我将实际代码作为脚本标记注入到
  • ASP.NET Core 中 AsNoTracking 的模拟或更好的解决方法

    您如何模拟 AsNoTracking 或者是否有更好的解决方法来解决此问题 Example public class MyContext MyContextBase Constructor public MyContext DbContex
  • Webpack - 资产大小限制中的警告:以下资产超出了建议的大小限制 (244 KiB)

    当我在生产模式下运行 webpack 时 有资产规模限制 超出 的警告 我怎样才能运行而不出现这个错误 在我的项目中 我包含 css 并且我看到 webpack 构建中包含一些 node module 目录 但是如果我排除 css 的 no
  • ArgumentMatchers.any 不得为 null

    我正在尝试测试ViewModel以确保实时数据得到正确更新 然而使用时ArgumentMatchers any 它失败了IllegalStateException saying ArgumentMatchers any mViewModel
  • 如何获取 Spotify API 的访问令牌?

    我已经研究 Spotify api 和示例源代码几天了 但我仍然不知道如何获取访问令牌来访问用户的播放列表数据 我已经到达了拉起登录窗口 用户登录 然后收到授权码的地步 此时 我尝试做这样的事情 window open https acco
  • Lightbox:如何翻译“Image x of x”文本?

    我使用 Lightbox 2 作为图像集 当我的网站的访问者单击该集中的缩略图时 它将显示 图像的放大版本 下面是 描述 取自 a 标题属性 其下方 文本 Image x of x 例如 Image 1 of 12 有谁知道在哪里翻译 更改
  • 如何处理 setTimeout() 的多个实例?

    阻止创建 setTimeout 函数的多个实例 在 JavaScript 中 的最推荐 最佳方法是什么 一个例子 伪代码 function mouseClick moveDiv div 0001 mouseX mouseY function
  • 如何在使用类型分散时将箭头添加到行尾

    如何在 y 不等于 0 且系列类型以线宽 2 分散的情况下正确地将箭头添加到行的每一端 在这里我可以看到箭头已添加但未正确添加 请看这个 部分工作小提琴 http jsfiddle net vnYCX 这是我的 JS 最初的原型是由 sta
  • jQuery 模板插件:如何创建双向绑定?

    我开始使用 jQuery 模板插件 微软创建的 但现在我面临这个问题 模板用于绑定到对象数组的一堆表单 当我更改其中一个表单上的某些内容时 我希望更新绑定的对象 但我不知道如何自动执行该操作 这是一个简单的例子 现实生活中的模板和对象要复杂
  • Kendo 刷新 (DropDownList.refresh()) 不起作用错误未定义

    我试图在另一个 DropDownList 更改后刷新下拉列表 但 Refresh 方法未定义错误正在升级 我尝试再次读取数据源 它显示它正在加载 但数据仍然相同 帮助解决这个问题请 Code DropDownList1 change fun
  • 为什么我从 c# 到 js 得到不同的 MD5 哈希值?

    我有一个用于加密密码的 C 函数 System Security Cryptography MD5CryptoServiceProvider md5Provider new System Security Cryptography MD5C
  • 禁用移动设备上的锚点菜单点击

    我使用嵌套列表作为带有子菜单项的菜单 我曾经这样做过 如果您将鼠标悬停在主菜单项上 子菜单项将通过将显示从无更改为块来出现 我决定让子菜单看起来就像是下拉的 并使用了 CSS 过渡 我遇到的问题是 在第一种方法中 如果您触摸 iPad 上的
  • 如何使用 Chart.js 版本 3.2.1 在圆环图中添加文本

    我正在使用 Canvas 在 HTML 中使用 如何使用在圆环图中添加文本 这是我的 javascript 代码和 HTML 代码 我使用了图表js版本3 2 1 所以请给出相同版本 3 的解决方案 var overallStatsCanv
  • 为什么 phantomjs 不能在 MacOS Sierra 中工作?

    我们正在使用phantomjs 1 9 1 macosx phantomjs 2 0 0 macosx哪一个工作得很好OS X 埃尔卡皮坦更新后macOS 塞拉利昂它会引发以下错误 phantomjs 1 9 1 macosx phanto

随机推荐

  • 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