首页 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

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

热门文章

<🎜>:泡泡胶模拟器无穷大 - 如何获取和使用皇家钥匙
4 周前 By 尊渡假赌尊渡假赌尊渡假赌
北端:融合系统,解释
4 周前 By 尊渡假赌尊渡假赌尊渡假赌
Mandragora:巫婆树的耳语 - 如何解锁抓钩
3 周前 By 尊渡假赌尊渡假赌尊渡假赌

热工具

记事本++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教程
1671
14
CakePHP 教程
1428
52
Laravel 教程
1331
25
PHP教程
1276
29
C# 教程
1256
24
Python vs. JavaScript:学习曲线和易用性 Python vs. JavaScript:学习曲线和易用性 Apr 16, 2025 am 12:12 AM

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

从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等概念,增强了灵活性和异步编程能力。

JavaScript和Web:核心功能和用例 JavaScript和Web:核心功能和用例 Apr 18, 2025 am 12:19 AM

JavaScript在Web开发中的主要用途包括客户端交互、表单验证和异步通信。1)通过DOM操作实现动态内容更新和用户交互;2)在用户提交数据前进行客户端验证,提高用户体验;3)通过AJAX技术实现与服务器的无刷新通信。

JavaScript在行动中:现实世界中的示例和项目 JavaScript在行动中:现实世界中的示例和项目 Apr 19, 2025 am 12:13 AM

JavaScript在现实世界中的应用包括前端和后端开发。1)通过构建TODO列表应用展示前端应用,涉及DOM操作和事件处理。2)通过Node.js和Express构建RESTfulAPI展示后端应用。

了解JavaScript引擎:实施详细信息 了解JavaScript引擎:实施详细信息 Apr 17, 2025 am 12:05 AM

理解JavaScript引擎内部工作原理对开发者重要,因为它能帮助编写更高效的代码并理解性能瓶颈和优化策略。1)引擎的工作流程包括解析、编译和执行三个阶段;2)执行过程中,引擎会进行动态优化,如内联缓存和隐藏类;3)最佳实践包括避免全局变量、优化循环、使用const和let,以及避免过度使用闭包。

Python vs. JavaScript:社区,图书馆和资源 Python vs. JavaScript:社区,图书馆和资源 Apr 15, 2025 am 12:16 AM

Python和JavaScript在社区、库和资源方面的对比各有优劣。1)Python社区友好,适合初学者,但前端开发资源不如JavaScript丰富。2)Python在数据科学和机器学习库方面强大,JavaScript则在前端开发库和框架上更胜一筹。3)两者的学习资源都丰富,但Python适合从官方文档开始,JavaScript则以MDNWebDocs为佳。选择应基于项目需求和个人兴趣。

Python vs. JavaScript:开发环境和工具 Python vs. JavaScript:开发环境和工具 Apr 26, 2025 am 12:09 AM

Python和JavaScript在开发环境上的选择都很重要。1)Python的开发环境包括PyCharm、JupyterNotebook和Anaconda,适合数据科学和快速原型开发。2)JavaScript的开发环境包括Node.js、VSCode和Webpack,适用于前端和后端开发。根据项目需求选择合适的工具可以提高开发效率和项目成功率。

C/C在JavaScript口译员和编译器中的作用 C/C在JavaScript口译员和编译器中的作用 Apr 20, 2025 am 12:01 AM

C和C 在JavaScript引擎中扮演了至关重要的角色,主要用于实现解释器和JIT编译器。 1)C 用于解析JavaScript源码并生成抽象语法树。 2)C 负责生成和执行字节码。 3)C 实现JIT编译器,在运行时优化和编译热点代码,显着提高JavaScript的执行效率。

See all articles