发布于 2026-01-06 0 阅读
0

比较 React 测试库

比较 React 测试库

作者:Murat Çatal ✏️

无论你创作的是什么,都应该在提供给其他人之前进行测试。这样,你就能在正式发布前对最终产品更有信心,也能更好地掌控它。

应用测试策略

测试应用程序的方法有很多种,从测试一小段代码到更通用的功能测试,应有尽有。在深入探讨相关的框架和库之前,让我们先来看看一些评估应用程序功能最有效的方法。

单元测试

单元测试会检查代码的每个小片段。你可以把它想象成测试各个基本组件在其生命周期内的功能。这通常是最简单、成本最低的测试方法。

集成测试

如果你有很多组件,你可能需要测试它们之间的交互方式。你可以通过模拟端点来实现这一点,这可以作为集成测试的一部分。但与单元测试相比,这种方法成本更高、更复杂。

端到端测试

当需要用真实数据测试整个系统,看看一切是否按预期运行时,端到端测试是你的最佳选择。

当你开始编写测试时,你可能会忍不住去修改组件的内部业务和测试实现细节,但这会让你走上错误的道路。相反,你应该从用户的角度编写测试,这样才能生成更清晰、更准确的测试用例。毕竟,最终用户并不关心组件的内部细节,他们只关心他们看到的内容。

既然我们已经建立了一些通用的最佳实践,接下来让我们更详细地了解一些最常用的测试框架和运行器。我们将分析它们的学习曲线、功能以及优缺点。

LogRocket 免费试用横幅

笑话

Jest是由 Facebook 创建和维护的测试框架。如果您使用 React 构建应用程序create-react-app,则无需任何配置即可开始使用 Jest。只需添加react-test-rendererJest@testing-library/react库即可进行快照测试和 DOM 测试。

使用 Jest,您可以:

  • 进行快照测试、并行化测试和异步方法测试
  • 模拟你的函数,包括第三方node_module
  • 执行多种断言方法
  • 查看代码覆盖率报告

现在让我们开始动手编写代码吧。

安装

假设您的应用程序是通过以下方式创建的create-react-app

// For snapshot test
yarn add -D react-test-renderer

// For DOM test
yarn add -D @testing-library/react
Enter fullscreen mode Exit fullscreen mode

对于未使用构建的现有应用程序create-react-app,请按照以下步骤操作:

  1. 添加依赖项。
yarn add --dev jest babel-jest @babel/preset-env @babel/preset-react react-test-renderer
Enter fullscreen mode Exit fullscreen mode
  1. 配置你的 Babel。
// babel.config.js
module.exports = {
presets: ['@babel/preset-env', '@babel/preset-react'],
};
Enter fullscreen mode Exit fullscreen mode
  1. 在你的代码中添加测试命令package.json
// package.json
{
"scripts": {
"test": "jest"
}
}
Enter fullscreen mode Exit fullscreen mode

测试结构

既然您已经向应用程序添加了测试文件,那么让我们深入了解一下测试结构的一些细节。

如下所示,create-react-app已配置为运行包含.spec.js.test.js文件的测试。

// MyComponent
export const MyComponent = ({ label }) => {
  return <div>{label}</div>;
};
Enter fullscreen mode Exit fullscreen mode

我们有一个简单的组件,它接收一个标签属性并将其显示在屏幕上。下一步是编写一个简单的测试来确保它能正确显示。

import React from "react";
import { cleanup, render } from "@testing-library/react";
import { MyComponent } from "./MyComponent";

afterEach(cleanup);

describe("MyCompnent", () => {
  test("should display label", () => {
    const { getByText } = render(<MyComponent label="Test" />);
    expect(getByText("Test")).toBeTruthy();
  });
});
Enter fullscreen mode Exit fullscreen mode

现在我们来看看我们要测试的功能。

afterAllbeforeAll

在当前测试文件中,测试完成后或测试开始前运行函数。您可以使用该函数清理数据库中创建的资源和模拟数据afterAll,或者在 . 中设置配置beforeAll

该函数可能会返回一个生成器或一个 Promise,并且它会等待你的 Promise 或生成器函数执行完毕后再继续执行。

// MyTestFile.test.js
afterAll(() => {
  cleanResources();
});

beforeAll(() => {
   setupMyConfig();
});

describe("MyComponent",() => {
   test("should do this..",() => {
      expect(prop).toBeTruthy();
   });
});
Enter fullscreen mode Exit fullscreen mode

afterAll当当前文件中所有测试都执行完毕后运行。

afterEachbeforeEach

afterAll与 `getTestCategory() ` 和 ` getTestCategory()`不同beforeAll,这些函数会在测试文件中的每个测试用例运行之前调用。通过使用 `getTestCategory()` beforeEach,您可以在每个测试用例运行之前创建数据库连接。最佳实践是,您应该使用 `getTestCategory()`afterAll在每次测试用例运行后删除已创建的 DOM 元素。

// MyTestFile.test.js
afterAll(() => {
  resetDomTree();
});

beforeAll(() => {
  createDomElement();
});

describe("MyComponent",() => {
   test("should do this..",() => {
      expect(prop).toBeTruthy();
   });

   test("should do that..",() => {
      expect(prop).toBeTruthy();
   });
});
Enter fullscreen mode Exit fullscreen mode

describe

此命令允许您将相关测试分组,以生成更清晰的输出。

describe("MyComponent",() => {
   test("should do this..",() => {
      expect(prop).toBeTruthy();
   });

   test("should do that..",() => {
      expect(prop).toBeTruthy();
   });
});
Enter fullscreen mode Exit fullscreen mode

快照测试

快照测试会生成类似 HTML 的输出,以便您查看组件的结构。如果您想查看 CSS 属性如何根据事件注入,它尤其有用。

import React from 'react';
import Link from '../Link.react';
import renderer from 'react-test-renderer';

test('renders correctly', () => {
  const tree = renderer
    .create(<Link page="http://www.mydomain.com">My Domain</Link>)
    .toJSON();
  expect(tree).toMatchSnapshot();
});

// generated snapshot
exports[`renders correctly 1`] = `
<a
  className="normal"
  href="http://www.mydomain.com"
  onMouseEnter={[Function]}
  onMouseLeave={[Function]}
>
  My Domain
</a>
`;
Enter fullscreen mode Exit fullscreen mode

模拟函数

在测试过程中进行模拟是您需要实现的核心功能之一。Jest 不仅可以模拟函数,还可以模拟模块,非常出色。

例如,假设你想测试一个获取用户的函数。它使用了axios,但我们不想访问真实的端点,因为那不是我们想要测试的内容。

import axios from 'axios';
import { Customers } from "./customers";

jest.mock('axios');

test('should fetch users', () => {
  const customers = [{name: 'Bob'}, {name: 'Jenny'}];
  const resp = {data: customers.find(c => c.name = 'Bob')};
  axios.get.mockResolvedValue(resp);

  return Customers.getByFilter("Bob").then(data => expect(data).toEqual({name: 'Bob'}));
});
Enter fullscreen mode Exit fullscreen mode

茉莉花

与 Jest 类似,Jasmine 也是一个 JavaScript 框架和测试运行器。但是,在使用 Jasmine 之前,您需要进行一些配置。

从优点来看,Jasmine 可以做以下几件事:

  • 异步函数测试
  • 模拟请求
  • 自定义相等性检查器断言
  • 自定义匹配器断言

至于缺点,Jasmine 不支持以下一些功能:

  • 快照测试
  • 代码覆盖率工具
  • 并行化(需要第三方工具)
  • 原生 DOM 操作(需要第三方工具,例如 JSDOM)

此外,Jasmine 只会查找.spec.js文件;您必须编辑其配置才能使其也查找.test.js文件。

安装

Jasmine 主要与 Enzyme 一起使用,因此您需要安装 Enzyme 并进行一些配置。

yarn add -D babel-cli \
            @babel/register \
            babel-preset-react-app \
            cross-env \
            enzyme \
            enzyme-adapter-react-16 \
            jasmine-enzyme \
            jsdom \
            jasmine
Enter fullscreen mode Exit fullscreen mode

使用以下命令初始化 Jasmine 项目。

yarn run jasmine init
Enter fullscreen mode Exit fullscreen mode

现在我们将一些配置文件放在 spec/helper 文件夹中。这些文件将用于 Babel、Enzyme 和 JSDOM。

// babel.js
require('@babel/register');

// for typescript
require('@babel/register')({
    "extensions": [".js", ".jsx", ".ts", ".tsx"]
});


// enzyme.js or enzyme.ts 
// be sure your file extension is .ts if your project is a typescript project
import jasmineEnzyme from 'jasmine-enzyme';
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

configure({ adapter: new Adapter() });

beforeEach(function() {
  jasmineEnzyme();
});


// jsdom.js

import {JSDOM} from 'jsdom';

const dom = new JSDOM('<html><body></body></html>');
global.document = dom.window.document;
global.window = dom.window;
global.navigator = dom.window.navigator;
Enter fullscreen mode Exit fullscreen mode

最后,编辑 Jasmine 配置文件,确保 Babel、Enzyme 和 JSDOM 配置正确加载。

现在是时候搬进去了spec/suppor/jasmine.json

// the important part here is we should load babel firstly.

// for normal projects
"helpers": [
  "helpers/babel.js",
  "helpers/**/*.js"
],

// for typescript projects
"helpers": [
  "helpers/babel.js",
  "helpers/**/*.{js,ts}"
],
Enter fullscreen mode Exit fullscreen mode

让我们回顾一下如何使用 Jasmine 编写测试。我们还会简要介绍一下 Enzyme。

大多数辅助函数,如afterAll`test`、beforeAll`set` afterEach、`set` 和beforeEach`set`,都与 Jest 类似,所以让我们深入了解如何为 React 组件编写一个基本测试,看看它的结构。

const Utils = React.addons.TestUtils;
let element;
beforeEach(() => {
  element = React.createElement(
      MyComponent,
      {
        label: 'Hello'
      });
});

afterEach(() => {
  element = null;
});

describe('MyComponent', function() {
  it('can render without error', function() {
    const component = Utils.renderIntoDocument(element);
    expect(component).not.toThrow();
  });
})
Enter fullscreen mode Exit fullscreen mode

自定义匹配器

在 Jasmine 中,您可以编写自定义匹配器函数,以便在每个测试规范中全局复用。例如,如果您有一组经常使用的测试匹配器,自定义匹配器就非常有用。

自定义匹配器应返回一个包含 `valid`passmessage`false` 属性的对象。`valid`pass属性用于检查条件是否处于有效状态。`false`message字段用于在失败状态下显示。

const customMatchers = {
  toBeValidAgeRange: function() {
    return {
      compare: function(actual, expected) {
         var result = {};
         result.pass = (actual > 18 && actual <=35);
         result.message = actual + ' is not valid';   
         return result;
      }
    };
  }
};


describe("Custom matcher", function() {
  beforeEach(function() {
    // register our custom matcher
    jasmine.addMatchers(customMatchers);
  });
  it("should be valid age", function() {
    expect(19).toBeValidAgeRange();
  });

  it("should fail", function() {
    expect(38).toBeValidAgeRange();
  });
});
Enter fullscreen mode Exit fullscreen mode

自定义相等性检查器

有时,您可能需要比较两个对象,或者更改相等性检查的行为以比较基本类型。Jasmine 提供了一个优秀的 API 来重写相等性检查。

自定义相等性检查函数必须有两个参数:第一个参数来自函数本身expect,第二个参数来自assertion函数。此外,它必须返回 trueboolean或 false undefined。如果返回 false undefined,则表示该相等性检查函数不适用于这些参数。

function myObjectChecker(first, second) {
  if (typeof first === 'object' && typeof second === 'object' && 
      first.hasOwnProperty('name') && second.hasOwnProperty('name')) {
    return first.name === second.name;
  }
}

beforeEach(() => {
  jasmine.addCustomEqualityTester(myObjectChecker);
});

describe('MyComponent', function() {
  it('can render without error', function() {
    expect({name: 'John'}).toEqual({name:'John'});
  });
});
Enter fullscreen mode Exit fullscreen mode

react-testing-library

该库由 Kent C. Dodds 创建,并由庞大的开发者社区维护,它使您能够在不触及组件内部业务的情况下测试组件——这反过来又使您能够执行更强大的测试用例,同时将用户体验放在首位。

有了它react-testing-library,你可以:

  • 查询文本中的元素label,,,,displayValueroletestId
  • 触发任何事件
  • 等待元素出现wait

但是,你不能:

  • 进行浅渲染
  • 访问组件的内部业务,例如状态

安装

yarn add -D @testing-library/react
Enter fullscreen mode Exit fullscreen mode

现在到了有趣的部分……

import React from 'react';
import { render, RenderOptions, RenderResult } from '@testing-library/react';

describe('MyComponent', () =&gt; {
  test('should label be in document', () =&gt; {
    const {container, util} = render(&lt;MyComponent label='Hello' /&gt;);
    const label = utils.getByText('Hello');
    expect(label).toBeInTheDocument();
  });
}
Enter fullscreen mode Exit fullscreen mode

API文档

Enzyme是一个 JavaScript 测试工具框架,旨在帮助开发者轻松测试 React 组件。它由 Airbnb 维护,是使用最广泛的框架之一。

酶能让你:

  • 使用浅渲染
  • 访问组件的业务实现
  • 执行完整的 DOM 渲染
  • 用于react-hooks浅渲染,但有一些限制

安装

yarn add -D enzyme enzyme-adapter-react-16
Enter fullscreen mode Exit fullscreen mode

创建一个enzyme.js文件src夹,如下所示。

import Enzyme, { configure, shallow, mount, render } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

configure({ adapter: new Adapter() });
export { shallow, mount, render };
export default Enzyme;
Enter fullscreen mode Exit fullscreen mode

现在我们来编写一些代码。

浅渲染

import React from 'react';
// we are importing from our enzyme.js
import { shallow } from './enzyme';

import MyComponent from './MyComponent';

describe('MyComponent', () =&gt; {
  test('renders correct text in item', () =&gt; {
    const wrapper = shallow(&lt;MyComponent label="Hello" /&gt;);

    //Expect the child of the first item to be an array
    expect(wrapper.find('.my-label').get(0).props.children).toEqual('Hello');
  });
});
Enter fullscreen mode Exit fullscreen mode

完整 DOM 渲染

describe('&lt;Foo /&gt;', () =&gt; {
  it('calls componentDidMount', () =&gt; {
    sinon.spy(Foo.prototype, 'componentDidMount');
    const wrapper = mount(&lt;Foo /&gt;);
    expect(Foo.prototype.componentDidMount).to.have.property('callCount', 1);
  });
}
Enter fullscreen mode Exit fullscreen mode

注意componentDidMount!我们访问了组件的内部业务,如果您不小心,可能会导致您编写错误的测试用例。

端到端测试

到目前为止,我们已经从编写单元测试或集成测试的角度探讨了测试库。然而,在正式上线之前,我们也可能需要与后端进行完全集成的测试。为此,我们将介绍两个库:Cypress 和 Puppeteer。

Cypress使您无需任何额外的测试框架即可编写测试。它提供了友好的 API 来与页面组件交互,并且支持 Chrome/Chromium、Canary 和 Electron。

你可以做什么;

  • 时间旅行
  • 屏幕截图和视频
  • 自动等待
  • 在不触及服务器的情况下控制网络流量,以测试极端情况
  • 并行化

请使用以下代码行分别安装和运行 Cypress。

yarn add -D cypress
yarn run cypress open
Enter fullscreen mode Exit fullscreen mode

现在我们来编写一些测试。

首先,创建一个名为 . 的文件my-test_spec.js

describe('My First Test', function() {
  it('Gets, types and asserts', function() {
    cy.visit('https://www.mydomain.com')
    cy.contains('login').click()

    cy.url().should('include', '/login')

    cy.get('.email')
      .type('my@email.com')
      .should('have.value', 'my@email.com')
  })
})
Enter fullscreen mode Exit fullscreen mode

木偶师

Puppeteer不是一个 JavaScript 测试框架,而是一个无头 Chromium 库。你可以启动 Chromium 浏览器,并使用其提供的 API 在页面之间导航、获取按钮并点击它们。

Puppeteer 运行在真正的浏览器上,使你能够使用类似于浏览器的 API 编写端到端测试。

要进行安装,请输入以下代码行。

yarn add -D jest-puppeteer puppeteer jest
Enter fullscreen mode Exit fullscreen mode

然后输入以下内容package.json

// package.json
{
 jest: {
    "preset": "jest-puppeteer"
  }
}
Enter fullscreen mode Exit fullscreen mode

以下是我们的端到端测试代码。

beforeAll(async ()=&gt; {
  await page.goTo('http://mydomain.com');
});

describe('Visit MyDomain', () =&gt; {
  test('should have login text', () =&gt; {
     await expect(page).toMatch('login');
  });
});
Enter fullscreen mode Exit fullscreen mode

对 React 测试库和框架进行正面比较

到目前为止,我们已经了解了库的特性以及如何在项目中实现它们。现在,让我们来看一些基准测试结果,并比较不同库的性能。

玩笑与茉莉

正如我们开头提到的,Jest 和 Jasmine 是测试框架。你需要将测试用例分组到 describe 代码块中,并在 testtestittest 函数中编写测试代码。

现在让我们用一个方便易读的表格来详细比较一下。

React 测试框架对比:Jest 与 Jasmine

我最喜欢 Jest 的以下几点:

  • 零配置要求
  • 快照测试支持
  • 代码覆盖率支持
  • 模拟函数

至于 Jasmine,它最有用的功能是模拟功能。虽然这个功能有些局限性,但足以满足大多数使用场景。

我目前在产品中使用 Jest,因为它在 React 社区中得到了原生支持,而且在测试 React 组件方面,它比 Jasmine 更能满足我们的需求。

react-testing-library与酶

编写组件测试时,最重要的考虑因素之一是组件的util函数。它们可能会迫使你编写更简洁、更准确的测试代码,也可能导致你因为导出的 API 而编写错误的测试。

React 测试库对比:react-testing-library 与 Enzyme

编写组件测试时,不要过于纠结于实现细节。记住,要尝试从用户的角度思考问题。这将有助于你编写出更好的测试套件,从而增强你对测试结果的信心。

大多数情况下,我更喜欢使用组件react-testing-library,主要是因为它的导出 API 不允许你使用组件的内部 API,这迫使你编写更好的测试。此外,它无需任何配置。

另一方面,Enzyme 允许您使用组件的内部 API,其中可以包含生命周期方法或状态。

react-testing-libraries在很多项目中都使用过酶和添加剂。不过,我发现添加剂通常react-testing-library能让事情变得更简单。

柏树大战傀儡师

在正式上线前,对关键页面进行端到端测试至关重要,这可能挽救你的系统。以下是 Cypress 和 Puppeteer 的简要对比。

React 测试库对比:Cypress 与 Puppeteer

由于 Cypress 是一个测试框架,因此在需要快速开发的应用场景中,它比 Puppeteer 具有诸多优势。Cypress 的 API 对开发者非常友好,允许开发者像编写单元测试一样编写测试用例。Puppeteer 并非测试框架,而是一个浏览器。它的 API 对开发者并不友好,但由于可以访问浏览器的 API,因此功能非常强大。正因如此,Puppeteer 的学习曲线比 Cypress 更为陡峭。

结论

正如您所见,每种测试方法、库和框架都有其自身的优势和不足,具体取决于用例和您希望分析的数据类型。考虑到这些因素,在评估了每个测试框架之后,很明显,react-testing-library对于单元测试和集成测试而言,它是最有价值且最合理的选择。对于端到端测试,Cypress 凭借其易于学习的 API 是一个合适的选择。


全面了解生产环境中的 React 应用

调试 React 应用可能很困难,尤其是在用户遇到难以重现的问题时。如果您有兴趣监控和跟踪 Redux 状态、自动发现 JavaScript 错误以及跟踪缓慢的网络请求和组件加载时间,不妨试试 LogRocket。

替代文字

LogRocket就像 Web 应用的 DVR,它会记录 React 应用中发生的一切。你无需猜测问题原因,即可汇总并报告问题发生时应用的状态。LogRocket 还会监控应用的性能,并提供客户端 CPU 负载、客户端内存使用情况等指标的报告。

LogRocket Redux 中间件包为用户会话增加了一层额外的可见性。LogRocket 会记录 Redux 存储中的所有操作和状态。

革新 React 应用的调试方式——立即开始免费监控。


这篇文章《比较 React 测试库》最初发表在LogRocket 博客上。

文章来源:https://dev.to/bnevilleoneill/comparing-react-testing-libraries-2oeh