2022 了,试试 Vitest 写写单测(含 Demo 和总结)

3,850 阅读4分钟

不喜勿喷,纯粹是个总结,喜欢就点个赞吧。🎉🎉🎉

What Vitest?

cn.vitest.dev/

Why Vitest?

cn.vitest.dev/guide/why.h…

GitHub Demo

vitest-shared

使用 Vite 快速构建一个初始项目

pnpm create vite share-vitest -- --template vue-ts

pnpm i

pnpm add @types/node -D

将 Vitest 添加到项目中

pnpm add vitest -D

pnpm add @vue/test-utils -D

配置 Vitest

  1. 增加 vitest.config.ts 文件;
  2. 新建 test 文件,在其中创建 setupFiles/index.ts 文件为后续做准备;
  3. 修改 vitest.config.ts 文件
import { defineConfig } from "vitest/config";
import path from "path";

const resolveFile = (file: string) => path.resolve(__dirname, file);

export default defineConfig({
  test: {
    // 运行在每个测试文件前面
    setupFiles: [resolveFile("./test/setupFiles/index.ts")],
  },
});
  1. package.json 文件中增加 test 脚本。

配置 MSW

官方推荐使用 Mock Service Worker 来模拟网络请求,同时支持模拟 REST 和 GraphQL 网络请求,并且跟所使用的框架没有任何联系。

安装

pnpm add msw -D

开始

创建 test/mocks/server 文件夹,新建 handlers.tsindex.ts 两个文件,其中 handlers.ts 文件用于存模拟 api 请求处理集合。

handlers.ts 文件新增一个请求文章列表的接口,并对外暴露,详情见该文件 handlers.ts

index.ts 文件中引入 setupServer ,并将 handlers 传入,详情见该文件 index.ts

最后在 setupFiles/index.ts 文件中监听该服务,结束用例后关闭该服务。

至此,在 node 端模拟 api 请求配置大致完毕,接下来使用 Express 配置一个小型的接口输出。

配置 Express

安装

pnpm add express -D

开始

创建 index.js 文件,并增加获取文章列表的接口,启动服务,监听端口号为 4430。

使用 node server/index.js 启动服务后,执行 pnpm dev 查看列表是否加载正常。

问题总结

如何设置全局 setup?

  1. 新建 test/vitest.config.ts 文件,在 test 中设置 setupFiles 配置;

    import { defineConfig } from "vitest/config";
    
    export default defineConfig({
      test: {
        // ...
        setupFiles: ["xxxx/setup.ts"],
      },
    });
    
  2. 若在导入 vitest/config 时 ts 报错,可以将版本升级至 0.5.1+ 版本,相应的官方修复记录如下:

    github.com/vitest-dev/…

如何 mock 模块内的方法?

import { vi } from "vitest";

vi.mock("@/my-module", () => {
  return {
    get: (x: string) => x,
  };
});

// mock node_module 模块
vi.mock("js-cookie", () => {
  return {
    // 使用 default,默认导出
    default: {
      get: (x: string) => x,
    },
  };
});

// x.spec.ts 文件
import { get } from "@/my-module";
import Cookies from "js-cookie";

Cookies.get(); // 以替换为 mock 的数据

如何解决 lodash-es 报错?

相关代码:

// ...
import merge from "lodash-es/merge";
// ...

相关的报错信息:

SyntaxError: Unexpected token 'export'
Module D:\xxx\node_modules\.pnpm\lodash-es@4.17.11\node_modules\lodash-es\_freeGlobal.js:4 seems to be an ES Module but shipped in a CommonJS package. You might want to create an issue to the package "D:\xxx\node_modules\.pnpm\lodash-es@4.17.11\node_modules\lodash-es\_freeGlobal.js:4" asking them to ship the file in .mjs extension or add "type": "module" in their package.json.

As a temporary workaround you can try to inline the package by updating your config:
// vitest.config.js
export default {
  test: {
    deps: {
      inline: [
        "D:\xxx\node_modules\.pnpm\lodash-es@4.17.11\node_modules\lodash-es\_freeGlobal.js:4"
      ]
    }
  }
}

其实提示信息已经给了我们解决问题的思路了,就是在 deps 中配置好 inline ,具体操作如下:

import { defineConfig } from "vitest/config";

export default defineConfig({
  test: {
    // ...
    setupFiles: ["xxxx/setup.ts"],
    deps: {
      inline: ["lodash-es"],
    },
  },
});

使用 msw mock api 请求报错或请求未被捕获问题

具体错误信息如下:

[MSW] Error: captured a request without a matching request handler:

  • GET http://localhost/api/policy

If you still wish to intercept this unhandled request, please create a request handler for it.
Read more: https://mswjs.io/docs/getting-started/mocks

引用官网的一句文案:

Bear in mind that without a DOM-like environment, like the jsdom from Jest, you must use absolute request URLs in NodeJS. This should be reflected in your request handlers:

Node - Getting Started

需要设置具体的请求路径,如果项目中使用的是 axios 库,可以设置 axios.defaults.baseURL = 'https://api.backend.dev' 即可,但使用 rest.get 等方式还是需要将路径写全。如下代码:

const server = setupServer(
  // NOT "/user", nothing to be relative to!
  rest.get("https://api.backend.dev/user", (req, res, ctx) => {
    return res(ctx.json({ firstName: "John" }));
  })
);

mock document 问题

happy-dom or jsdom for DOM mocking. 具体的配置方式可以参考官方文档:

Vitest

mock window.URL 问题

具体报错信息如下:

[error] TypeError: window.URL.createObjectURL is not a function

解决方案:

window.URL.createObjectURL = vi.fn((object: any) => object);

mock 对象某个属性值

实际在写单测场景时,想要修改某个传入对象中的属性值,那么直接使用 vi.mock 去解决显然是不合理的,因为这样会导致全局 mock ,导致影响其他模块的单测。

可以使用 vi.spyOn 解决该问题,而不影响到其他模块单元测试。

// @role
export const user = {
  isAdmin: false,
  isCustom: true,
};

// xx.spec.ts
import { user } from "@role";

vi.spyOn(user, "isAdmin", "get").mockImplementation(() => true);

若同个模块中其它测试场景下需要修改不同的属性值,可以使用 spyFn.mockRestore 解决。

it("xxx_1", () => {
  const spy = vi.spyOn(user, "isAdmin", "get").mockImplementation(() => true);
  expect(xxx).xxx();
  spy.mockRestore();
});

it("xxx_2", () => {
  const spy = vi.spyOn(user, "isAdmin", "get").mockImplementation(() => false);
  expect(xxx).xxx();
  spy.mockRestore();
});

vscode debugger 测试用例出现 Segmentation fault 问题

github.com/vitest-dev/…

考虑降 node 版本

vitest 使用 happy-dom 仍然存在 Element 等内置方法不存在问题

  1. 执行测试用例,控制台输出如下错误:

    [error] TypeError: ele.isEqualNode is not a function

    可以通过全局 mock 的方式解决: global.HTMLElement.prototype.isEqualNode = vi.fn();