上一篇文章介绍了使用装饰器简化路由定义的思路与简单实现,接下来配置 Jest 对 nflask 代码进行单元测试,保证代码的健壮性。同时借助 husky 和 lint-staged 在代码提交时检测代码规范,并借助 Github Actions 在代码提交到仓库之后检测单元测试覆盖率。
Jest 与 Typescript
使用 Jest 对 Typescript 文件做单元测试,官网提供了两种方式,一种是借助 Babel,另一种是使用 ts-jest。对于 nflask 项目而言使用 Babel 会带来额外的配置,使用 ts-jest 是最简单的方式。
安装依赖
pnpm add -D jest ts-jest @types/jest -w
添加配置文件
在 monorepo 项目根目录下,新建 jest.config.base.js 文件存放最基础通用的配置,以及 jest.config.js 作为 jest 执行的配置文件。
// jest.config.base.js
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
moduleFileExtensions: ['ts', 'js'],
testMatch: ['**/__tests__/**/*.+(ts|tsx|js)', '**/?(*.)+(spec|test).+(ts|tsx|js)'],
transform: {
'^.+\\ts$': 'ts-jest',
},
};
// jest.config.js
const base = require('./jest.config.base');
/** @type {import('ts-jest').JestConfigWithTsJest} */
const base = require('./jest.config.base');
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
...base,
verbose: true,
silent: true,
roots: ['<rootDir>'],
projects: ['<rootDir>/packages/nflask'],
collectCoverage: true,
collectCoverageFrom: ['!**/node_modules/**', '!**/dist/**', '**/src/**'],
coverageReporters: [['json', { file: 'report.json' }], 'lcov'],
erageThreshold: {
global: {
statements: 70,
functions: 80,
branches: 80,
lines: 80,
},
},
};
需要将项目添加到单元测试项目中时,需要在 jest.config.js 中的 projects
中添加相应的项目路径,同时在项目下添加 jest.config.js 文件,配置内容如下
const { pathsToModuleNameMapper } = require('ts-jest');
const base = require('../../jest.config.base');
const { compilerOptions } = require('./tsconfig.json');
const { name } = require('./package.json');
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
...base,
moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, {
prefix: '<rootDir>/',
}),
displayName: name,
};
修改 packages/nflask/tsconfig.json 文件,主要有两个部分需要修改
- 在
compilerOptions.paths
中添加"@nflask": ["./src"]
,实现对 src 目录下文件的引用别名 - 在
include
配置中添加test
目录,使得 Typescript 能够作用于单元测试文件
测试一下
假设有如下函数需要测试
// src/index.ts
export function sum(a: number, b: number) {
return a + b;
}
在 __tests__ 目录下新建 sum.ts
文件,并添加如下单元测试代码
import { sum } from '@nflask';
describe('sum module', () => {
test('adds 1 + 2 to equal 3', () => {
expect(sum(1, 2)).toBe(3);
});
});
接着在根目录下的 package.json 中添加执行单元测试脚本 jest
。
测试 nflask
编写单元测试代码前首先需要构造测试数据,对于 nflask
而言,需要在测试时构造一个 node 服务,并模拟使用装饰器包装的接口信息是否正确返回。这里借助 supertest 来启用一个单元测试使用的 node 服务。
构造 mock 数据
在 test 目录下新建一个 **mock** 文件夹,存放测试需要模拟的 API 项目代码。这里 mock 的文件结构,如下
__mock__/
controllers/ --
/v1 -- v1 版本 API
products.ts
/v2 -- v2 版本 API
product.ts
routes/ -- 路由层,将请求转向对应的 controller
services/ -- 服务层,业务逻辑
app.ts
编写单元测试代码
具体的单元测试代码可以查看这里,这里就不展开说了。
Husky 和 lint-staged 配置
为了统一代码风格,代码 lint 工具几乎每个项目都会有配置,除了文件保存自动格式化之外,每次代码提交之前也都应该进行一次 lint,当出现代码不符合规范时,拒绝提交代码。
宗旨就是:封杀一切不合规的代码
Husky 是一个设置 Git hooks 的开源工具。代码提交时,进行 lint 操作,最优的情况一定是只对 Git 暂存区的代码进行 lint 操作,lint-stage 就被用来实现这样的需求。
Husky 和 lint-staged 由于整个项目都需要使用的工具,因此安装依赖时需要使用 -w
pnpm add -D husky lint-staged -w
配置 Husky
安装完成之后,执行 pnpm husky install
对 Husky 进行初始化配置。初始化会新建一个 .husky 目录,并执行如下命令
pnpm husky add .husky/pre-commit "pnpm lint-staged"
pre-commit 是一个可执行文件,如果不是可执行文件权限,记得执行
chmod u+x .husky/pre-commit
当其他人克隆了当前项目,执行 pnpm install
时,一定也要初始化 Husky ,所以添加一个 prepare 钩子,让依赖安装之后自动执行 huksy install
。
配置 lint-staged
lint-staged 的配置支持直接在 package.json 文件中修改,也可以在项目目录下新建 .lintstagedrc 等格式的配置文件。具体定义方法,可以查看官方文档。
"lint-staged": {
"*.{ts,tsx}": [
"prettier --write",
"eslint --fix"
]
}
Commit Lint
偶然了解了 conventional commits,生成 CHANGELOG 会依据 Git Commit 时填写的信息,例如 feat: add husky and lint-staged 在 CHANGELOG 中就会归类并生产一条新的记录。既然有这么好用的工具,那代码提交时,也就需要对 commit 信息做校验。
依赖安装
pnpm add -D @commitlint/config-conventional @commitlint/cli -w
添加配置文件
echo "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.js
使用 Husky 添加 hook
pnpm husky add .husky/commit-msg 'npx --no -- commitlint --edit ${1}'
CI 执行单元测试
之前关于博客部署到服务器的文章提到了 Github Actions 的配置内容,这里就不在赘述,直接附上单元测试 Job 的配置。
name: Test
on:
pull_request:
branches:
- main
jobs:
coverage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version: 16
- uses: pnpm/action-setup@v2
name: Install pnpm
id: pnpm-install
with:
version: 7
run_install: false
- name: Get pnpm store directory
id: pnpm-cache
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
name: Setup pnpm cache
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Install Dependencies
run: pnpm install --frozen-lockfile
- uses: ArtiomTr/jest-coverage-report-action@v2
with:
skip-step: install
test-script: pnpm test
base-coverage-file: report.json
借助 Jest coverage report 插件,在测试完成之后把测试结果显示在 PR 上。
![success](/_next/image?url=%2Fstatic%2Fimages%2Fttask%2Fgithub-test-success.png&w=3840&q=75)
额外的需求
上面的 CI 配置,对所有目标分支为 main 的 PR 做了单元测试,当单元测试没有通过时,希望 Merge Pull Request 的按钮处于无法点击的状态,此时我们就可以通过在仓库 settings 中开启保护分支状态检测选项来实现需求。
![setting](/_next/image?url=%2Fstatic%2Fimages%2Fttask%2Fgithub-protected-branch-settings.png&w=3840&q=75)
当单元测试未通过时,就会像下图一样,Merge Pull Request 被置灰了。
![disable](/_next/image?url=%2Fstatic%2Fimages%2Fttask%2Fgithub-pr-disable.png&w=3840&q=75)
当然按钮上方的复选框选中之后,即使单元测试未通过,也仍然可以合并 PR。要想严格限制,不允许绕过状态检测的话,可以在保护分支的配置中开启 Do not allow bypassing the above settings 选项