初识 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"];
    }
相关推荐
Myli_ing12 分钟前
HTML的自动定义倒计时,这个配色存一下
前端·javascript·html
dr李四维30 分钟前
iOS构建版本以及Hbuilder打iOS的ipa包全流程
前端·笔记·ios·产品运营·产品经理·xcode
雯0609~1 小时前
网页F12:缓存的使用(设值、取值、删除)
前端·缓存
℘团子এ1 小时前
vue3中如何上传文件到腾讯云的桶(cosbrowser)
前端·javascript·腾讯云
学习前端的小z1 小时前
【前端】深入理解 JavaScript 逻辑运算符的优先级与短路求值机制
开发语言·前端·javascript
彭世瑜1 小时前
ts: TypeScript跳过检查/忽略类型检查
前端·javascript·typescript
FØund4041 小时前
antd form.setFieldsValue问题总结
前端·react.js·typescript·html
Backstroke fish1 小时前
Token刷新机制
前端·javascript·vue.js·typescript·vue
小五Five1 小时前
TypeScript项目中Axios的封装
开发语言·前端·javascript
小曲程序1 小时前
vue3 封装request请求
java·前端·typescript·vue