Jest 简介:单元测试、模拟和异步代码
笑话简介
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 API、setTimeout、HTTP 请求 等。这可以帮助我们创建一些异步,非阻塞行为
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中文网其他相关文章!

热AI工具

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

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

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

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

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

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

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

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

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

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

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

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

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