打开网易新闻 查看精彩图片

前端团队每年在API Mock上浪费的时间,够重构半个项目。你写过那种「临时用一下」的Mock脚本吗?三天后它就成了技术债,两周后没人敢碰,一个月后连作者自己都看不懂。

更糟的是测试环境。Jest里写一套Mock,Storybook里再写一套,E2E测试里还得写第三套。同样的接口逻辑复制三遍,改一个字段要改三个地方。这不是开发,这是体力劳动。

MSW(Mock Service Worker)的解法很刁钻:它不在代码层拦截,直接在网络层动手脚。浏览器里用Service Worker(服务工作者线程)截获请求,Node.js里用自定义拦截器——你的业务代码完全感知不到被「骗」了。

「透明」是最高级的Mock

「透明」是最高级的Mock

传统Mock的问题在于侵入性。你要改import路径,要注入Mock实例,要在生产代码里留一堆if (isMock)的判断。MSW的哲学是:Mock应该像空气,存在但不可见。

看个实际配置。handlers(处理器)文件定义一次,到处复用:

打开网易新闻 查看精彩图片

import { http, HttpResponse } from 'msw'; export const handlers = [ http.get('/api/users', () => { return HttpResponse.json([ { id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }, ]); }), http.post('/api/users', async ({ request }) => { const user = await request.json(); return HttpResponse.json({ id: 3, ...user }, { status: 201 }); }), ];

浏览器环境启动Worker,Node环境启动Server,同一套handlers(处理器)无缝切换。你的fetch()、axios、ky—— whatever,全部中招,零代码改动。

这种设计有个隐蔽的好处:它逼你按真实HTTP语义写Mock。不是返回一个假对象,而是返回带状态码、Headers、Body的完整Response。你的错误处理逻辑、加载状态、重试机制,都能在Mock阶段被真实触发。

测试场景的「瑞士军刀」

测试场景的「瑞士军刀」

单元测试里最烦的是什么?不是写测试,是维护Mock。后端接口改了,Mock没同步,测试通过但线上崩——这种 false positive(假阳性)比测试失败更危险。

MSW的server.use() API(应用程序接口)允许运行时覆盖。某个测试需要特殊响应?临时插一条handler,测试结束自动清理:

打开网易新闻 查看精彩图片

test('shows user profile', async () => { server.use( http.get('/api/users/1', () => { return HttpResponse.json({ id: 1, name: 'Test User', role: 'admin' }); }) ); render(); expect(await screen.findByText('Test User')).toBeInTheDocument(); });

error case(错误场景)也能精确模拟。401未授权、500服务器错误、网络超时——不是改个布尔值,是返回真实的HttpResponse.error(),让你的错误边界组件真正被锻炼到。

从开发到生产的「一致性幻觉」

从开发到生产的「一致性幻觉」

MSW最被低估的能力,是制造环境一致性。开发时用的Mock,和测试时用的Mock,逻辑完全一致。不会出现「本地好好的,CI(持续集成)就挂」的玄学问题。

路径参数、查询参数、请求体的解析,全部按标准Web API实现。写Mock时顺便复习了URLSearchParams和Request对象,一举两得。

有个细节很有意思:MSW的浏览器端依赖Service Worker,这意味着它必须运行在HTTPS或localhost。这个限制反而成了安全网——你不会 accidentally(意外地)把Mock代码打包到生产环境,因为Service Worker在非安全上下文根本注册不了。

Node.js端则完全脱离浏览器约束,SSR(服务端渲染)场景、测试框架、甚至CLI(命令行界面)工具里都能跑。同一套抽象,两种实现,这个分寸感拿捏得很准。

目前MSW在GitHub(代码托管平台)收获超过2万星标,周下载量稳定在150万次以上。不是因为它功能多,是因为它恰好解决了那个最痛的点:Mock不该是项目里的二等公民,它应该和主代码一样被认真对待、被类型约束、被版本管理。