Skip to content

常见的测试框架

这两个测试框架, 配置起来比较简单, API 兼容性也比较好

为什么使用框架?

为了快速验证

js
function sayHi(username) {
  return 'hi,' + username;
}

// 手动验证
if (sayHi() === '') {
  // 通过验证
} else {
  // 未通过验证
}

// 框架 API 验证
expect(sayHi('tom')).toBe('hi,tom');

分类 API

  • describe: 分类(会再命令行输出的时候,进行分类)
  • it/test: 测试用例
  • 修饰符:
    • skip: 跳过这个测试用例
    • todo: 只会执行这个测试用例
    • only: 不会执行, 相当于一个标记, 可以先写测试, 在写实现代码
js
// describe.skip 也可以用在 describe 上
// describe.todo 也可以用在 describe 上
// describe.only 也可以用在 describe 上
describe('stack', () => {
  it('普通测试用例', () => {});
  it.skip('跳过这个测试用例, 不会执行', () => {});
  it.only('只会执行这个测试用例', () => {});
  it.todo('不会执行, 相当于一个标记, 可以先写测试, 在写实现代码', () => {});
});

断言 API

js
expect(1).toBe(1);
expect(1).not.toBe(2);
expect(1).toBeTruthy(2);
expect(1).toBeTypeOf('number');
expect({ id: 1 }).toEqual({ id: 1 });
expect(() => throw new Error('err')).toThrow();
expect(() => throw new Error('err')).toThrowError('err');
// 快照测试
expect(/*..*/).toMatchInlineSnapshot();
expect(/*..*/).toMatchSnapshot();

生命周期 API

  • beforeEach/afterEach
  • beforeAll/afterAll
js
beforeAll(() => {
  // 所有例子执行之前执行, 只会执行一次
  console.log('beforeAll-hook');
});

afterAll(() => {
  // 所有例子执行之后执行, 只会执行一次
  console.log('afterAll');
});

beforeEach(() => {
  // 每个测试用例执行前执行
  console.log('beforeEach');
});

afterEach(() => {
  // 每个测试用例执行后执行
  console.log('afterEach');
});

describe('stack', () => {
  it('should add value to items', () => {
    const stack = new Stack();
    const val = 'hello';
    stack.push(val);
    expect(stack.items.includes(val)).toBe(true);
  });
});

vitest 配置

typescript
/// <reference types="vitest" />
import { defineConfig } from 'vite';
import { resolve } from 'path';
import vue from '@vitejs/plugin-vue';

/* prettier-ignore */
export default defineConfig({
  plugins: [vue()],
  test: {
    // 是否开启自动全局(测试文件)导入vitest API
    globals: true,

    // 模拟浏览器DOM环境的包,允许的值有 happy-dom 或 jsdom
    environment: 'happy-dom',

    // vitest 启动时, 会执行的文件, 一般是用于设置测试代码环境
    // 比如测试vue组件, 需要 vue-router, 不可能每个测试文件都写一次
    // setupFiles: [
    //    resolve('./src/__tests__/setups/router-mock.ts'),
    // ],
  },

  // 配置路径别名
  resolve: {
    alias: {
      "@": resolve("./src"),
    },
  },
});
  • tsconfig.json
json
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "useDefineForClassFields": true,
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "skipLibCheck": true,

    /* Bundler mode */
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "preserve",

    /* Linting */
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true,

    /* path: 让编辑器识别 vite 配置中的 @ 路径别名 */
    "baseUrl": "./",
    "paths": {
      "@/*": ["./src/*"]
    },

    /* types: vitest/globals 不用全局导入, 就可以在测试文件中, 直接使用 expect toBe 等 API */
    "types": ["vite/client", "vitest/globals"]
  },
  "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
  "references": [{ "path": "./tsconfig.node.json" }]
}
  • tsconfig.node.json
json
{
  "compilerOptions": {
    "composite": true,
    "skipLibCheck": true,
    "module": "ESNext",
    "moduleResolution": "bundler",
    "allowSyntheticDefaultImports": true
  },
  "include": ["vite.config.ts"]
}
  • vite-env.d.ts
typescript
/// <reference types="vite/client" />

interface ImportMeta {
  readonly env: ImportMetaEnv;
}

// docs: https://cn.vitejs.dev/guide/env-and-mode.html#modes
interface ImportMetaEnv {
  readonly VITE_APP_BASE_URL: string;
}

declare module '*.vue' {
  import type { DefineComponent } from 'vue';
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  const component: DefineComponent<object, object, any>;
  export default component;
}

Released under the MIT License.