初识 jest

jest

jest 需要配合 git 使用,因为 jest 会检测 git 的提交记录,然后运行相关的测试用例。

运行 jest --watch 时,终端会有一下提示:

js 复制代码
› Press a to run all tests.
› Press f to run only failed tests.
› Press p to filter by a filename regex pattern.
› Press t to filter by a test name regex pattern.
› Press q to quit watch mode.
› Press Enter to trigger a test run.
  • a 运行所有测试用例
  • f 只运行失败的测试用例,如果一开始成功,后来改失败了,不会运行
  • p 用正则匹配文件名,只运行匹配到这个文件名的测试用例
  • t 用正则匹配测试用例名,只运行匹配到这个名字的测试用例

异步测试

  1. 对于异步函数 fetchData 的测试,jest 默认不会等待异步测试用例执行完毕,所以需要在测试用例中传入一个 done 函数,然后在异步操作完成后调用这个函数

    go 复制代码
    const fetchData = (fn) => {
      fetch("http://localhost:8080/ping").then(async (response) => {
        if (response.ok) {
          const res = await response.json();
          fn(res);
        } else {
          fn(response.status);
        }
      });
    };
    
    test("fetchData", (done) => {
      fetchData((data) => {
        expect(data).toEqual({ message: "pong" });
        done();
      });
    });
  2. 修改异步函数 fetchData,返回一个 Promise 对象

    js 复制代码
    const fetchData = () => {
      return new Promise((resolve, reject) => {
        fetch("http://localhost:8080/ping").then(async (response) => {
          if (response.ok) {
            resolve(await response.json());
          } else {
            reject(response.status);
          }
        });
      });
    };
    test("fetchData", () => {
      // 需要将 return 的 Promise 对象返回,否则 jest 会认为测试用例已经执行完毕
      return fetchData().then((data) => {
        expect(data).toEqual({ message: "pong" });
      });
    });
  3. 测试 reject 报错的情况,通过 catch 方法捕获错误,需要在测试用例中添加 expect.assertions(1),因为异步请求响应正确的话 catch 分支不会被执行

    js 复制代码
    test("fetchData", () => {
      expect.assertions(1);
      return fetchData().catch((data) => {
        expect(data).toEqual(404);
      });
    });
  4. 通过 toMatchObject 方法判断

    js 复制代码
    test("fetchData", () => {
      return expect(fetchData()).resolves.toMatchObject({
        message: "pong",
      });
    });
  5. 测试 reject 报错情况

    js 复制代码
    test("fetchData", () => {
      // 如果抛出的是 throw new Error(),则使用 toThrow
      // return expect(fetchData()).rejects.toThrow();
      return expect(fetchData()).rejects.toEqual(404);
    });

mock

现在有个函数接收一个回调函数,并执行这个函数,测试用例应该怎么写呢?

js 复制代码
const callback = (cb) => {
  cb();
};

jest 提供了一个 jest.fnmock 函数来测试

js 复制代码
test("mock", () => {
  const mockFn = jest.fn();
  callback(mockFn);
  expect(mockFn).toBeCalled();
});

mock 更高级的用法是,可以模拟请求的响应,在实际的测试中,我们不需要等待网络请求的响应,只需要模拟一个请求的响应即可

如果项目中使用 fetch 发起网络请求,需要使用下面的方法替代 fetch

js 复制代码
global.fetch = jest.fn();

然后在测试用例中使用 mockImplementationOnce 或者 mockResolvedValue 模拟请求的响应

  • mockImplementationOnce

    js 复制代码
    fetch.mockImplementationOnce(() =>
      Promise.resolve({
        json: () => Promise.resolve({ message: "pong" }),
        text: () => Promise.resolve("mocked text"),
        ok: true,
        status: 200,
        statusText: "ok",
      })
    );
  • mockResolvedValue

    js 复制代码
    fetch.mockResolvedValue(
      Promise.resolve({
        json: () => Promise.resolve({ message: "pong" }),
        text: () => Promise.resolve("mocked text"),
        ok: true,
        status: 200,
        statusText: "ok",
      })
    );
js 复制代码
test("mock", async () => {
  fetch.mockImplementationOnce(() =>
    Promise.resolve({
      json: () => Promise.resolve({ message: "pong" }),
      text: () => Promise.resolve("mocked text"),
      ok: true,
      status: 200,
      statusText: "ok",
    })
  );

  await fetchData().then((res) => {
    expect(res).toEqual({ message: "pong" });
  });
});

mock 高级用法

在当前目录下新建 __mocks__ 目录

新建文件 fetchData.js,写一个 fetchData 函数,然后在测试用例中引入这个函数

js 复制代码
export const fetchData = () => {
  return Promise.resolve({ message: "pong" });
};

在文件的开头用 jest.mock 引入这个文件,jest.mock 会自动到 __mocks__ 目录下找对应的文件(可以通过配置文件 jest.config.js 属性 automock 来配置)

import 导入的函数就会去 __mocks__ 目录下找对应的文件

js 复制代码
jest.mock("./fetchData.js");
import { fetchData } from "./fetchData";
test("fetchData", () => {
  return fetchData().then((data) => {
    expect(data).toEqual({ message: "pong" });
  });
});

如果想要引入真实 ./fetchData.js 文件中的函数,可以使用 jest.requireActual 方法

js 复制代码
const {} = jest.requireActual("./fetchData.js");

snapshot

jest 提供了 snapshot 功能,可以将测试用例的结果保存到快照文件中,然后在下次测试时,将测试用例的结果与快照文件中的结果进行比较,如果不一致,则测试用例失败

jest 会在项目的根目下生成一个 __snapshots__ 文件夹,里面存放着测试用例的快照文件

测试不通过的话,按 u 键更新所有测试用例的快照文件,按 i 键一个个更新测试用例的快照文件

js 复制代码
test("snapshot", () => {
  expect(generateConfig()).toMatchSnapshot();
});

如果是个动态的数据,可以给 toMatchSnapshot 传入一个参数

js 复制代码
expect(generateConfig()).toMatchSnapshot({
  time: expect.any(Date),
});

定时器

定义一个 setTimeout 定时器,每隔 3 秒执行一次回调函数

js 复制代码
export const timer = (cb) => {
  setTimeout(() => {
    cb();
    setTimeout(() => {
      cb();
    }, 3000);
  }, 3000);
};

我们在测试时,不需要等待 3 秒,可以使用 jest 提供的 jest.advanceTimersByTime 方法,来快速执行定时器

js 复制代码
jest.useFakeTimers();

test("timer", () => {
  const mockFn = jest.fn();
  timer(mockFn);
  jest.advanceTimersByTime(6000); // 快速执行 2 个定时器
  expect(mockFn).toHaveBeenCalledTimes(2);
});

test("timer", () => {
  const mockFn = jest.fn();
  timer(mockFn);
  jest.advanceTimersByTime(3000); // 快速执行 1 个定时器
  expect(mockFn).toHaveBeenCalledTimes(1);
  jest.advanceTimersByTime(3000); // 再快速执行 1 个定时器
  expect(mockFn).toHaveBeenCalledTimes(2);
});

jest 还有两个方法可以快速执行定时器:

  • jest.runAllTimers 方法可以快速执行所有定时器
  • jest.runOnlyPendingTimers 方法可以快速执行队列中的待执行的定时器(只执行第一层的 setTimeout

jest 配置

  • collectCoverageFrom 配置需要收集测试覆盖率的文件

    • 表示分析 src 目录下的 jsjsxtstsx 文件,但是排除 src 目录下的 .d.ts 文件
    js 复制代码
    {
      collectCoverageFrom: ["src/**/*.{js,jsx,ts,tsx}", "!src/**/*.d.ts"];
    }
  • setupFiles 配置在测试用例运行之前需要运行的文件

    js 复制代码
    {
      setupFiles: ["react-app-polyfill/jsdom"];
    }
  • setupFilesAfterEnv 配置在测试用例运行之后需要做一些其他事情

    js 复制代码
    {
      setupFilesAfterEnv: ["<rootDir>/src/setupTests.js"];
    }
  • testMatch 配置测试文件的匹配规则

    js 复制代码
    {
      testMatch: ["<rootDir>/src/**/__tests__/**/*.{js,jsx,ts,tsx}", "<rootDir>/src/**/*.{spec,test}.{js,jsx,ts,tsx}"];
    }
  • testEnvironment 配置测试环境

    js 复制代码
    {
      testEnvironment: "jsdom";
    }
  • transform 配置需要转换的文件

    • 当以 .js.jsx.mjs.cjs.ts.tsx 结尾时,使用 babelTransform.js 文件进行转换
    js 复制代码
    {
      transform: {
        "^.+\\.(js|jsx|mjs|cjs|ts|tsx)$": "<rootDir>/config/jest/babelTransform.js",
        "^.+\\.css$": "<rootDir>/config/jest/cssTransform.js",
        "^(?!.*\\.(js|jsx|mjs|cjs|ts|tsx|css|json)$)": "<rootDir>/config/jest/fileTransform.js"
      };
    }
  • transformIgnorePatterns 配置不需要转换的文件

    js 复制代码
    {
      transformIgnorePatterns: [
        "[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs|cjs|ts|tsx)$",
        "^.+\\.module\\.(css|sass|scss)$",
      ];
    }
  • modulePaths 配置模块路径

  • moduleNameMapper 配置模块映射

    • 当引入 css module 这样的文件时,identity-obj-proxy 会返回一个对象,这个对象的属性名和属性值是一样的
    js 复制代码
    {
      moduleNameMapper: {
        "^.+\\.module\\.(css|sass|scss)$": "identity-obj-proxy",
      },
    };
  • moduleFileExtensions 配置模块文件扩展名

    js 复制代码
    {
      moduleFileExtensions: ["js", "jsx", "ts", "tsx", "json", "node"];
    }
  • watchPlugins 配置监听插件

    js 复制代码
    {
      watchPlugins: ["jest-watch-typeahead/filename", "jest-watch-typeahead/testname"];
    }
相关推荐
清灵xmf1 小时前
在 Vue 中实现与优化轮询技术
前端·javascript·vue·轮询
大佩梨1 小时前
VUE+Vite之环境文件配置及使用环境变量
前端
GDAL2 小时前
npm入门教程1:npm简介
前端·npm·node.js
小白白一枚1112 小时前
css实现div被图片撑开
前端·css
薛一半3 小时前
PC端查看历史消息,鼠标向上滚动加载数据时页面停留在上次查看的位置
前端·javascript·vue.js
@蒙面大虾3 小时前
CSS综合练习——懒羊羊网页设计
前端·css
MarcoPage3 小时前
第十九课 Vue组件中的方法
前端·javascript·vue.js
.net开发3 小时前
WPF怎么通过RestSharp向后端发请求
前端·c#·.net·wpf
**之火3 小时前
Web Components 是什么
前端·web components
顾菁寒3 小时前
WEB第二次作业
前端·css·html