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
用正则匹配测试用例名,只运行匹配到这个名字的测试用例
异步测试
-
对于异步函数
fetchData
的测试,jest
默认不会等待异步测试用例执行完毕,所以需要在测试用例中传入一个done
函数,然后在异步操作完成后调用这个函数goconst 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(); }); });
-
修改异步函数
fetchData
,返回一个Promise
对象jsconst 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" }); }); });
-
测试
reject
报错的情况,通过catch
方法捕获错误,需要在测试用例中添加expect.assertions(1)
,因为异步请求响应正确的话catch
分支不会被执行jstest("fetchData", () => { expect.assertions(1); return fetchData().catch((data) => { expect(data).toEqual(404); }); });
-
通过
toMatchObject
方法判断jstest("fetchData", () => { return expect(fetchData()).resolves.toMatchObject({ message: "pong", }); });
-
测试
reject
报错情况jstest("fetchData", () => { // 如果抛出的是 throw new Error(),则使用 toThrow // return expect(fetchData()).rejects.toThrow(); return expect(fetchData()).rejects.toEqual(404); });
mock
现在有个函数接收一个回调函数,并执行这个函数,测试用例应该怎么写呢?
js
const callback = (cb) => {
cb();
};
jest
提供了一个 jest.fn
的 mock
函数来测试
js
test("mock", () => {
const mockFn = jest.fn();
callback(mockFn);
expect(mockFn).toBeCalled();
});
mock
更高级的用法是,可以模拟请求的响应,在实际的测试中,我们不需要等待网络请求的响应,只需要模拟一个请求的响应即可
如果项目中使用 fetch
发起网络请求,需要使用下面的方法替代 fetch
js
global.fetch = jest.fn();
然后在测试用例中使用 mockImplementationOnce
或者 mockResolvedValue
模拟请求的响应
-
mockImplementationOnce
jsfetch.mockImplementationOnce(() => Promise.resolve({ json: () => Promise.resolve({ message: "pong" }), text: () => Promise.resolve("mocked text"), ok: true, status: 200, statusText: "ok", }) );
-
mockResolvedValue
jsfetch.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
目录下的js
、jsx
、ts
、tsx
文件,但是排除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"]; }