首页 web前端 js教程 Jest 简介:单元测试、模拟和异步代码

Jest 简介:单元测试、模拟和异步代码

Nov 01, 2024 am 12:23 AM

Introduction to Jest: Unit Testing, Mocking, and Asynchronous Code

笑话简介

Jest 是一个用于测试 JavaScript 代码的库。

这是一个由 Facebook 维护的开源项目,它特别适合 React 代码测试,但不仅限于此:它可以测试任何 JavaScript 代码。它的优点是:

  • 速度很快
  • 它可以执行快照测试
  • 它固执己见,提供开箱即用的一切,无需您做出选择
export default function sum(a, n) {
  return a + b;
}
登录后复制
登录后复制
登录后复制
登录后复制

divide.test.js

import sum from './sum';

// Describe the test and wrap it in a function.
it('adds 1 + 2 to equal 3', () => {
  const result = sum(1, 2);

  // Jest uses matchers, like pretty much any other JavaScript testing framework.
  // They're designed to be easy to get at a glance;
  // here, you're expecting `result` to be 3.
  expect(result).toBe(3);
});
登录后复制
登录后复制
登录后复制
登录后复制

匹配器

匹配器是一种可以让您测试值的方法。

  • toBe 比较严格相等,使用 ===
  • toEqual 比较两个变量的值。如果它是一个对象或数组,它会检查所有属性或元素的相等性
  • 传递 null 值时 toBeNull 为 true
  • 传递定义值时 toBeDefined 为 true(与上面相反)
  • 传递未定义值时 toBeUndefine 为 true
  • toBeCloseTo 用于比较浮点值,避免舍入错误
  • toBeTruthy true 如果该值被认为是 true (就像 if 那样)
  • toBeFalsy 如果该值被认为是假(就像 if 一样),则为 true
  • 如果 Expect() 的结果高于参数
  • ,则 toBeGreaterThan true
  • toBeGreaterThanOrEqual 如果 Expect() 的结果等于参数或高于参数
  • ,则为 true
  • 如果 Expect() 的结果低于参数
  • ,则 toBeLessThan true
  • toBeLessThanOrEqual 如果 Expect() 的结果等于参数或低于参数
  • ,则为 true
  • toMatch 用于将字符串与正则表达式模式匹配进行比较
  • toContain 用于数组,如果预期数组在其元素集中包含参数
  • ,则为 true
  • toHaveLength(number):检查数组的长度
  • toHaveProperty(key, value):检查对象是否具有属性,并可选择检查其值
  • toThrow 检查您传递的函数是否抛出异常(一般情况下)或特定异常
  • toBeInstanceOf():检查对象是否是类的实例

依赖关系

依赖项是您的应用程序所依赖的一段代码。它可以是我们项目中的函数/对象或第三方依赖项(例如 axios)

当您自己的应用程序没有一段代码就无法运行时,它就成为真正的依赖项。

例如,如果您在应用程序中实现一项功能来发送电子邮件或发出 api 请求或构建配置对象等

两种方法我们可以在js项目的代码中添加依赖项:

进口

export default function sum(a, n) {
  return a + b;
}
登录后复制
登录后复制
登录后复制
登录后复制

依赖注入

只是一个简单概念的奇特术语。

如果您的函数需要外部依赖项的某些功能,只需将其作为参数注入即可。

import sum from './sum';

// Describe the test and wrap it in a function.
it('adds 1 + 2 to equal 3', () => {
  const result = sum(1, 2);

  // Jest uses matchers, like pretty much any other JavaScript testing framework.
  // They're designed to be easy to get at a glance;
  // here, you're expecting `result` to be 3.
  expect(result).toBe(3);
});
登录后复制
登录后复制
登录后复制
登录后复制

单元测试

单元测试由软件开发人员编写和运行,以确保应用程序的一部分(称为“单元”)满足其设计并按预期运行。

我们想要单独测试我们的代码,我们不关心任何依赖项的实际实现。
我们想要验证

  • 我们的代码单元按预期工作
  • 返回预期结果
  • 按其应有的方式调用任何协作者(依赖项)

这就是模拟我们的依赖关系发挥作用的地方。

嘲笑

在单元测试中,模拟为我们提供了存根依赖项所提供的功能的能力,以及意味着观察我们的代码如何与依赖项交互。

当将依赖项直接包含到我们的测试中成本昂贵或不切实际时,例如,当您的代码对 API 进行 HTTP 调用或与数据库层交互时,模拟特别有用。

最好删除这些依赖项的响应,同时确保它们按要求被调用。这就是模拟派上用场的地方。

通过使用模拟函数,我们可以知道以下内容:

  • 收到的来电数量
  • 参数 每次调用时使用的值。
  • 每次调用时的“上下文”或这个值。
  • 函数如何退出以及产生了哪些值

开玩笑地嘲笑

创建模拟函数有多种方法。

  • jest.fn 方法允许我们直接创建一个新的模拟函数。
  • 如果您正在模拟对象方法,则可以使用 jest.spyOn。
  • 如果你想模拟整个模块,你可以使用 jest.mock。

jest.fn 方法本身就是一个高阶函数。

这是一个工厂方法,用于创建新的、未使用的模拟函数。

JavaScript 中的函数是一等公民,它们可以作为参数传递。

每个模拟函数都有一些特殊的属性。模拟属性是基础。此属性是一个对象,其中包含有关如何调用函数的所有模拟状态信息。该对象包含三个数组属性:

  • 调用 [每次调用的参数]
  • 实例 [每次调用时的“this”值]
  • Results [函数退出的值],results 属性有类型(return 或 throw)和值
    • 函数显式返回一个值。
    • 函数运行完成,没有 return 语句(相当于返回 undefined
    • 函数抛出错误。
export default function sum(a, n) {
  return a + b;
}
登录后复制
登录后复制
登录后复制
登录后复制
  • https://codesandbox.io/s/implementing-mock-functions-tkc8b

模拟基础

import sum from './sum';

// Describe the test and wrap it in a function.
it('adds 1 + 2 to equal 3', () => {
  const result = sum(1, 2);

  // Jest uses matchers, like pretty much any other JavaScript testing framework.
  // They're designed to be easy to get at a glance;
  // here, you're expecting `result` to be 3.
  expect(result).toBe(3);
});
登录后复制
登录后复制
登录后复制
登录后复制

模拟注入的依赖项

import { name, draw, reportArea, reportPerimeter } from './modules/square.js';
登录后复制
登录后复制

模拟模块

使用 jest.fn 模拟函数

// Constructor Injection

// DatabaseManager class takes a database connector as a dependency
class DatabaseManager {
    constructor(databaseConnector) {
        // Dependency injection of the database connector
        this.databaseConnector = databaseConnector;
    }

    updateRow(rowId, data) {
        // Use the injected database connector to perform the update
        this.databaseConnector.update(rowId, data);
    }
}

// parameter injection, takes a database connector instance in as an argument; easy to test!
function updateRow(rowId, data, databaseConnector) {
    databaseConnector.update(rowId, data);
}

登录后复制
登录后复制

这种类型的嘲笑不太常见,原因如下:

  • jest.mock 自动为模块中的所有函数执行此操作
  • jest.spyOn 做同样的事情,但允许恢复原始功能

使用 jest.mock 模拟模块

更常见的方法是使用 jest.mock 自动将模块的所有导出设置为 Mock 函数

// 1. The mock function factory
function fn(impl = () => {}) {
  // 2. The mock function
  const mockFn = function(...args) {
    // 4. Store the arguments used
    mockFn.mock.calls.push(args);
    mockFn.mock.instances.push(this);
    try {
      const value = impl.apply(this, args); // call impl, passing the right this
      mockFn.mock.results.push({ type: 'return', value });
      return value; // return the value
    } catch (value) {
      mockFn.mock.results.push({ type: 'throw', value });
      throw value; // re-throw the error
    }
  }
  // 3. Mock state
  mockFn.mock = { calls: [], instances: [], results: [] };
  return mockFn;
}
登录后复制
登录后复制

使用 jest.spyOn 监视或模拟函数

有时您只想观看一个方法被调用,但保留原始实现。其他时候,您可能想要模拟实现,但稍后在套件中恢复原始版本。

test("returns undefined by default", () => {
  const mock = jest.fn();

  let result = mock("foo");

  expect(result).toBeUndefined();
  expect(mock).toHaveBeenCalled();
  expect(mock).toHaveBeenCalledTimes(1);
  expect(mock).toHaveBeenCalledWith("foo");
});
登录后复制
登录后复制

恢复原来的实现

const doAdd = (a, b, callback) => {
  callback(a + b);
};

test("calls callback with arguments added", () => {
  const mockCallback = jest.fn();
  doAdd(1, 2, mockCallback);
  expect(mockCallback).toHaveBeenCalledWith(3);
});
登录后复制
登录后复制

JavaScript 和事件循环

JavaScript 是单线程的: 一次只能运行一个任务。通常这没什么大不了的,但现在想象一下你正在运行一个需要 30 秒的任务.. 是的.. 在该任务期间,我们等待 30 秒,然后才会发生其他事情(JavaScript 默认在浏览器的主线程上运行,所以整个UI都卡住了)。
现在是 2020 年,没有人想要一个缓慢、反应迟钝的网站。

幸运的是,浏览器为我们提供了一些 JavaScript 引擎本身不提供的功能:Web API。这包括 DOM APIsetTimeoutHTTP 请求 等。这可以帮助我们创建一些异步非阻塞行为

export default function sum(a, n) {
  return a + b;
}
登录后复制
登录后复制
登录后复制
登录后复制
  • 调用堆栈 - 当我们调用一个函数时,它会被添加到称为调用堆栈的东西中。
  • WebAPI - setTimeout 由 WebAPI 提供,采用回调函数并设置计时器,而不阻塞主线程
  • 队列 - 当计时器结束时,回调被添加到队列
  • 事件循环 - 检查调用堆栈是否为空,检查队列中是否有要执行的回调,然后移至要执行的调用堆栈
import sum from './sum';

// Describe the test and wrap it in a function.
it('adds 1 + 2 to equal 3', () => {
  const result = sum(1, 2);

  // Jest uses matchers, like pretty much any other JavaScript testing framework.
  // They're designed to be easy to get at a glance;
  // here, you're expecting `result` to be 3.
  expect(result).toBe(3);
});
登录后复制
登录后复制
登录后复制
登录后复制

使用 Jest 测试异步代码

Jest 通常希望同步执行测试的函数

如果我们执行异步操作,但我们不让 Jest 知道它应该等待测试结束,则会给出误报。

import { name, draw, reportArea, reportPerimeter } from './modules/square.js';
登录后复制
登录后复制

异步模式
JavaScript 中有多种处理异步操作的模式;最常用的是:

  • 回调
  • Promise 和异步/等待

测试回调

你不能在回调中进行测试,因为 Jest 不会执行它 - 测试文件的执行在回调被调用之前结束。要解决此问题,请将参数传递给测试函数,您可以方便地调用“done”。 Jest 将等到您调用 done() 后再结束测试:

// Constructor Injection

// DatabaseManager class takes a database connector as a dependency
class DatabaseManager {
    constructor(databaseConnector) {
        // Dependency injection of the database connector
        this.databaseConnector = databaseConnector;
    }

    updateRow(rowId, data) {
        // Use the injected database connector to perform the update
        this.databaseConnector.update(rowId, data);
    }
}

// parameter injection, takes a database connector instance in as an argument; easy to test!
function updateRow(rowId, data, databaseConnector) {
    databaseConnector.update(rowId, data);
}

登录后复制
登录后复制

承诺

对于返回 Promise 的函数,我们从测试中返回一个 Promise:

// 1. The mock function factory
function fn(impl = () => {}) {
  // 2. The mock function
  const mockFn = function(...args) {
    // 4. Store the arguments used
    mockFn.mock.calls.push(args);
    mockFn.mock.instances.push(this);
    try {
      const value = impl.apply(this, args); // call impl, passing the right this
      mockFn.mock.results.push({ type: 'return', value });
      return value; // return the value
    } catch (value) {
      mockFn.mock.results.push({ type: 'throw', value });
      throw value; // re-throw the error
    }
  }
  // 3. Mock state
  mockFn.mock = { calls: [], instances: [], results: [] };
  return mockFn;
}
登录后复制
登录后复制

异步/等待

为了测试返回 Promise 的函数,我们还可以使用 async/await,这使得语法非常简单明了:

test("returns undefined by default", () => {
  const mock = jest.fn();

  let result = mock("foo");

  expect(result).toBeUndefined();
  expect(mock).toHaveBeenCalled();
  expect(mock).toHaveBeenCalledTimes(1);
  expect(mock).toHaveBeenCalledWith("foo");
});
登录后复制
登录后复制

尖端

  • 我们需要很好地理解我们的函数的作用以及我们要测试的内容
  • 思考我们正在测试的代码的行为
  • 设置舞台:
    • 模拟/监视任何依赖项
    • 我们的代码是否与全局对象交互?我们也可以嘲笑/监视他们
    • 我们的测试是否与 DOM 交互?我们可以构建一些假元素来使用
    • 构建你的测试
    • 鉴于...
    • 当我打电话时......
    • 然后...我期待......
const doAdd = (a, b, callback) => {
  callback(a + b);
};

test("calls callback with arguments added", () => {
  const mockCallback = jest.fn();
  doAdd(1, 2, mockCallback);
  expect(mockCallback).toHaveBeenCalledWith(3);
});
登录后复制
登录后复制

链接

  • https://medium.com/@rickhanlonii/understanding-jest-mocks-f0046c68e53c
  • https://jestjs.io/docs/en/mock-functions
  • https://codesandbox.io/s/implementing-mock-functions-tkc8b
  • https://github.com/BulbEnergy/jest-mock-examples
  • https://dev.to/lydiahallie/javascript-visualized-event-loop-3dif
  • https://jestjs.io/docs/en/asynchronous
  • https://www.pluralsight.com/guides/test-asynchronous-code-jest

以上是Jest 简介:单元测试、模拟和异步代码的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

热门话题

Java教程
1663
14
CakePHP 教程
1420
52
Laravel 教程
1315
25
PHP教程
1266
29
C# 教程
1239
24
神秘的JavaScript:它的作用以及为什么重要 神秘的JavaScript:它的作用以及为什么重要 Apr 09, 2025 am 12:07 AM

JavaScript是现代Web开发的基石,它的主要功能包括事件驱动编程、动态内容生成和异步编程。1)事件驱动编程允许网页根据用户操作动态变化。2)动态内容生成使得页面内容可以根据条件调整。3)异步编程确保用户界面不被阻塞。JavaScript广泛应用于网页交互、单页面应用和服务器端开发,极大地提升了用户体验和跨平台开发的灵活性。

JavaScript的演变:当前的趋势和未来前景 JavaScript的演变:当前的趋势和未来前景 Apr 10, 2025 am 09:33 AM

JavaScript的最新趋势包括TypeScript的崛起、现代框架和库的流行以及WebAssembly的应用。未来前景涵盖更强大的类型系统、服务器端JavaScript的发展、人工智能和机器学习的扩展以及物联网和边缘计算的潜力。

JavaScript引擎:比较实施 JavaScript引擎:比较实施 Apr 13, 2025 am 12:05 AM

不同JavaScript引擎在解析和执行JavaScript代码时,效果会有所不同,因为每个引擎的实现原理和优化策略各有差异。1.词法分析:将源码转换为词法单元。2.语法分析:生成抽象语法树。3.优化和编译:通过JIT编译器生成机器码。4.执行:运行机器码。V8引擎通过即时编译和隐藏类优化,SpiderMonkey使用类型推断系统,导致在相同代码上的性能表现不同。

JavaScript:探索网络语言的多功能性 JavaScript:探索网络语言的多功能性 Apr 11, 2025 am 12:01 AM

JavaScript是现代Web开发的核心语言,因其多样性和灵活性而广泛应用。1)前端开发:通过DOM操作和现代框架(如React、Vue.js、Angular)构建动态网页和单页面应用。2)服务器端开发:Node.js利用非阻塞I/O模型处理高并发和实时应用。3)移动和桌面应用开发:通过ReactNative和Electron实现跨平台开发,提高开发效率。

Python vs. JavaScript:学习曲线和易用性 Python vs. JavaScript:学习曲线和易用性 Apr 16, 2025 am 12:12 AM

Python更适合初学者,学习曲线平缓,语法简洁;JavaScript适合前端开发,学习曲线较陡,语法灵活。1.Python语法直观,适用于数据科学和后端开发。2.JavaScript灵活,广泛用于前端和服务器端编程。

如何使用Next.js(前端集成)构建多租户SaaS应用程序 如何使用Next.js(前端集成)构建多租户SaaS应用程序 Apr 11, 2025 am 08:22 AM

本文展示了与许可证确保的后端的前端集成,并使用Next.js构建功能性Edtech SaaS应用程序。 前端获取用户权限以控制UI的可见性并确保API要求遵守角色库

从C/C到JavaScript:所有工作方式 从C/C到JavaScript:所有工作方式 Apr 14, 2025 am 12:05 AM

从C/C 转向JavaScript需要适应动态类型、垃圾回收和异步编程等特点。1)C/C 是静态类型语言,需手动管理内存,而JavaScript是动态类型,垃圾回收自动处理。2)C/C 需编译成机器码,JavaScript则为解释型语言。3)JavaScript引入闭包、原型链和Promise等概念,增强了灵活性和异步编程能力。

使用Next.js(后端集成)构建多租户SaaS应用程序 使用Next.js(后端集成)构建多租户SaaS应用程序 Apr 11, 2025 am 08:23 AM

我使用您的日常技术工具构建了功能性的多租户SaaS应用程序(一个Edtech应用程序),您可以做同样的事情。 首先,什么是多租户SaaS应用程序? 多租户SaaS应用程序可让您从唱歌中为多个客户提供服务

See all articles