0Pricing
Frontend Academy · 课时

模拟模块与 API 调用

使用 jest.mock() 模拟 ES 模块,使用 MSW(模拟服务工作器)替代 fetch,并测试依赖外部数据但无需真实网络调用的组件。

模拟模块与 API 调用 是 CoddyKit 上的免费 Frontend Academy 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Frontend Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Frontend Academy 课程共包含 4 节课。

为什么要使用模拟

单元测试应当彼此隔离。真实的 API 调用速度慢、不可靠且成本高。模拟会用受控的测试替身替换依赖,并返回可预测的响应。

jest.mock()——模拟整个模块

jest.mock('module-path') 会用自动模拟的版本替换整个模块。所有导出内容都会变成 jest.fn() 测试替身。调用 mockReturnValue 或 mockResolvedValue 来设置返回值。

jest.mock('./api');
import { fetchUser } from './api';

(fetchUser as jest.Mock).mockResolvedValue({ id: 1, name: 'Alice' });

test('renders user name', async () => {
  render(<Profile userId="1" />);
  await screen.findByText('Alice');
  expect(fetchUser).toHaveBeenCalledWith('1');
});

jest.fn()——手动测试替身

jest.fn() 会创建一个可以跟踪和控制的模拟函数。它适用于回调属性和服务依赖。

const onSubmit = jest.fn();
render(<Form onSubmit={onSubmit} />);

await user.click(screen.getByRole('button', { name: /submit/i }));

expect(onSubmit).toHaveBeenCalledOnce();
expect(onSubmit).toHaveBeenCalledWith({ email: 'alice@example.com' });

beforeEach 中重置模拟

在测试之间重置模拟,以防止状态泄漏。在 beforeEach 中使用 jest.clearAllMocks()。

beforeEach(() => {
  jest.clearAllMocks(); // reset call counts and implementations
});

afterAll(() => {
  jest.restoreAllMocks(); // restore original implementations
});

MSW——模拟服务工作线程

MSW 在网络层拦截真实的 fetch/XHR 调用:在浏览器中使用服务工作线程,在测试中使用 Node.js 拦截器。编写能够返回模拟响应的请求处理程序。

import { setupServer } from 'msw/node';
import { http, HttpResponse } from 'msw';

const server = setupServer(
  http.get('/api/users', () => {
    return HttpResponse.json([{ id: 1, name: 'Alice' }]);
  })
);

beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

MSW 按测试覆盖

针对特定的测试场景覆盖默认处理程序。

test('handles server error', async () => {
  server.use(
    http.get('/api/users', () => new HttpResponse(null, { status: 500 }))
  );

  render(<UserList />);
  await screen.findByText(/server error/i);
});

模拟 localStorage

在使用 localStorage 的测试中,使用 jest.spyOn 模拟 Web 存储方法。

beforeEach(() => {
  const store: Record<string, string> = {};
  jest.spyOn(Storage.prototype, 'getItem').mockImplementation((key) => store[key] ?? null);
  jest.spyOn(Storage.prototype, 'setItem').mockImplementation((key, val) => { store[key] = val; });
});

模拟计时器

jest.useFakeTimers() 会将 setTimeout/setInterval 替换为由 Jest 控制的版本。jest.runAllTimers() 会让所有计时器快速执行完毕。

jest.useFakeTimers();

test('shows toast for 3 seconds', () => {
  render(<Toast message="Saved!" />);
  expect(screen.getByText('Saved!')).toBeInTheDocument();

  jest.advanceTimersByTime(3000);
  expect(screen.queryByText('Saved!')).not.toBeInTheDocument();
});

afterEach(() => jest.useRealTimers());

模块工厂模式

对于 TypeScript,模拟模块工厂会返回正确的类型。

jest.mock('./services/auth', () => ({
  login: jest.fn().mockResolvedValue({ token: 'test-token' }),
  logout: jest.fn()
}));

快照测试——谨慎使用

快照测试会保存组件输出的序列化表示,并在其发生变化时失败。快照很容易创建,但可能产生噪声较大的误报。请仅将其用于稳定的纯展示组件。

快速检查

与使用 jest.mock('fetch') 模拟 API 相比,MSW 的主要优势是什么?

回顾:模拟

jest.mock() 会替换模块。jest.fn() 会创建可跟踪的存根。在 beforeEach 中重置模拟。MSW 会在网络层拦截 HTTP,这是最贴近真实情况的模拟方式。针对错误场景,可以使用每个测试中的 server.use() 覆盖设置。对于依赖时间的代码,请使用虚假计时器。组件集成测试应优先使用 MSW。

常见问题解答

「模拟模块与 API 调用」课时是免费的吗?

是的 — 「模拟模块与 API 调用」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Frontend Academy 课程的其余内容,请升级到 CoddyKit PRO。 Frontend Academy 课程共包含 4 节课。

「模拟模块与 API 调用」这节课中我会学到什么?

使用 jest.mock() 模拟 ES 模块,使用 MSW(模拟服务工作器)替代 fetch,并测试依赖外部数据但无需真实网络调用的组件。 你通过在浏览器中直接运行的动手代码来练习 Frontend Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Frontend Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Frontend Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。

「模拟模块与 API 调用」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Frontend Academy 课中编写并运行代码吗?

能。每节 Frontend Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. Jest 设置与基础测试
  2. @testing-library/react:render 与 userEvent
  3. @testing-library/vue:挂载组件
  4. 模拟模块与 API 调用
← 返回 Frontend Academy