V8 引擎使得前端的工程化成为可能,前端的被细分到组件。这里将演示使用 Cypress 对组件进行测试

Cypress(官网),是一个自动测试套件,可以让用户可视化地进行端对端集成测试,或者是组件测试。与 jest + testing-library 这种注重单测的组合不同,Cypress 提供更加丰富的界面交互。 当我们开发组件库 (Components) 而不是应用 (Application) 的时候,往往没有一个可以打开的页面让我们查看到组件的样子,不像依赖于构建工具,像 Webpack、Vite 等,提供了方便的实时功能,组件库的开发不需要一个 index.html 文件。那么使用 Cypress 这样的可视化测试套件就是很好的选择。

file

组件库伊始

从头创建一个简单的组件库,一开始可能是像这样的简单的目录结构:

- src
	- index.css
	- index.tsx
- package.json
- tsconfig.json

因为是 React-ts 项目,所以这里有 tsconfig.jsonpackage.json 里也只有简单的 React 依赖:

{
  // ...
  "dependencies": {
    "react": "^18.2.0"
  },
  "devDependencies": {
    "@types/react": "^18.2.45"
  }
}

还有 index.tsxindex.css 内容如下,它的效果是一个类似于苹果窗口:

file

但是在我们编写的代码的时候怎么才能看到组件的效果呢?(;´д`)ゞ

当然可以用 vite 直接构建一个应用,将组件塞到应用里展示。但是这样却更像是开发单页应用:

  1. 你能够看到目录下的 index.html 文件是组件完全无关的功能
  2. 使用 vite build 后的程序入口 dist/index.html 也不是我们的组件入口
  3. 如果将你的组件库发布到 npm 上,也多了很多组件外的代码

而 Cypress 可以作为组件的测试、可视化工具,可以让你的项目目录干净很多:

file

打包后的 dist 目录十分干净,index.js 就是组件的入口,当然这需要打包工具的配合,我使用的是 rollup

那赶紧端上来罢~(恼)

安装 Cypess

在项目根目录下运行命令 npm i -D cypresspackage.json 配置测试命令脚本:

"scripts": {
  "test": "cypress open"
}

第一次的配置

然后运行 npm test

一阵鬼畜之后会打开一个窗口:

file

这是是让你选择要 E2E 测试还是 组件测试,我们当然是组件测试。因为这里是我们在项目里第一次使用 Cypress 测试,可以看到两个测试类别下面都有 Not Configured 的标识。 点击 Component Testing 后会让你进行一些配置,首先是前端框架和打包工具的选择:

file

这里使用的是 React 框架,打包工具我选择的是 Vite。然后点击 Next Step,下一步需要你安装对应的包,这里会罗列出需要你安装的包和接受的版本,已经安装好的在右侧是一个打勾的状态,顶部还贴心地提供了安装的命令,但是这里要注意的是,如果直接使用它提供的命令,可能会有版本不符的情况。比如我现在写这篇的文章的时候,Vite 的最新版是 5.x,如果直接使用它提供的命令:npm install -D vite react-dom 将安装最新的 5.x 的 Vite,但是列表里需要的版本并不包括 5.x,所以要特别注意,安装 Vite 的时候使用 npm i -D vite@4.0.0

file

在安装好依赖后所有的列表右侧会变成打勾状态,左下角的按钮会变成 Continue,点击继续,

file

这里是 Cypress 帮我们添加好了配置文件,点击 Continue 继续,然后会看到这个东西:

file

这是因为虽然我们指定了 Vite 为打包工具,但是我们的目录下并没有 vite.config.ts 或相关的配置文件,所以先去弄一个。 在根目录下 vite.config.ts,内容如下:

import { defineConfig } from 'vite'

export default defineConfig({
})

然后回到 Cypress 的窗口,点击上面的 Try again,这次它会检测到有了 vite.config.ts,并出现这个界面,要我们选择展示组件的浏览器:

file

选一个你喜欢的就好。然后会弹出另一个测试界面:

file

此时我们还没有创建任何的测试,点击 Create from component,Cypress 会检测我们的组件,选择一个开始测试:

file

它帮我们生成了一些默认的测试代码:

file

这样就好,点击 Okay,在出现的界面中就能够看到我们组件的样子了,

file

左侧是组件列表,右侧是我们选中的组件的样式。回到我们的代码里,在我们的组件的同目录中会出现一个测试文件:indexAppleBox.cy.tsx,我们对当前组件的测试就写在这里了:

file

初始代码的问题

上面的图里的代码是 Cypress 给我们默认生成的测试代码,你能够看到在 describe、it、cy、AppleBox 下分别有四条红色波浪线,describe、it、cy 的红色波浪线是因为 ts 编译器找不到这几个变量的定义,AppleBox 的是因为没有传递给组件必传的参数: Content。Cypress 也不是面面俱到的。

修复 AppleBox 的容易,随便传递一个值给 Content 就可以;describe、it、cy 定义的缺失需要在 tsconfig.json 的 include 节点添加 "cypress",在 compilerOptions -> types 节点添加 "cypress",然后在当前组件的测试文件 indexAppleBox.cy.tsx 中 添加引用 import { mount } from 'cypress/react18',并将 cy.mount 改成 mount 即可,同时,如果 tsconfig.json 有提示类似的警告:Cannot write file '*********/cypress/support/commands.js' because it would overwrite input file.,就在 compilerOptions 节点下添加 "outDir": "dist"(更详细的看这里:typescript-error-cannot-write-file-because-it-would-overwrite-input-file)。改完后的就像这样:

{
  "compilerOptions": {
	"outDir": "dist",
    ...
    "types": ["cypress", "node"]
  },
  "include": ["src/**/*", "cypress"]
	...
}

// indexAppleBox.cy.tsx

import React from 'react'
import { AppleBox } from './index'
import { mount } from 'cypress/react18'

describe('<AppleBox />', () => {
  it('renders', () => {
    // see: https://on.cypress.io/mounting-react
    mount(<AppleBox content='CONTENT'/>)
  })
})

上面的问题虽然不修复 Cypress 也可以跑,但是对于我们后续的打包会有影响,还是修复了好。

查询和断言

当然现在简陋的测试代码并不能算上是测试,只是将组件渲染出来而已,Cypress 也提供了查询组件、断言的 API。当使用 mount 加载了组件后,要使用查询 API 查找组件,通过使用断言 API 检查组件是否按照预期中运行。 这个部分就很形象 jest + testing-library 的测试库了,Cypress 也提供了这样的功能,十分强大!

查询

在组件挂在后,需要查询获取到这个组件,在 Cypress 里提供了丰富的查询 API,常用的是 cy.get(),它接受类似 CSS 选择器的语法,比如 cy.get('.container') 查询类名包含 container 的元素;cy.get('[data-cy="container"]') 查询属性名 data-cy 的值为 container 的元素。更多丰富的查询 API 看官方的文档

获取到元素之后就要使用断言检查。

断言

Cypress 的断言库使用的是 Chai,提供了丰富的断言功能,Cypress 断言文档。 断言 API 可以在查询 API 之后链式调用,如使用 should() 断言组件存在。

cy.get('[data-cy="container"]').should('exist');

然后在 Cypress 窗口里查看,能看到左侧执行了 mount、get、assert,在断言部分成功的,能看到是绿色高亮的。

file

如果将测试代码改成这样 cy.get('[data-cy="container"]').should('not.exist');,它是断言失败的,会用红色高亮,并给出详细信息:

file

更详细的 API 在文档里,太多了不想写。

最佳实践

Cypress 特别详细说明了使用查询和断言的最佳实践,Best Practices。 如:

  • 不要使用变量来存放查询到的组件,这样就无法获取到组件的最新状态,要每次都使用查询 API 获取组件。
  • 不要使用查询元素、类名、ID来获取组件,组件的类名、ID 可能非常容易改变,会使你的测试用例不稳定。要在组件上添加属性 data-cy,使用 cy.get('[data-cy="xxx"]') 来查询组件。

等等非常多的用例,详细查看文档。

总结

Cypress 对于 E2E 的测试和组件的测试都是非常便利的。