如何为 bulletproof-react 的 MSW mock API 定义数据模型并生成测试数据? 如何为 bulletproof-react 的 MSW mock API 定义数据模型并生成测试数据【免费下载链接】bulletproof-react️ ⚛️ A simple, scalable, and powerful architecture for building production ready React applications.项目地址: https://gitcode.com/GitHub_Trending/bu/bulletproof-reactbulletproof-react 的apps/react-vite应用用 MSWMock Service Worker在开发阶段模拟 API在 service worker 里拦截所有 HTTP 请求返回 handler 定义好的响应。要让 MSW 返回像真后端一样的数据需要同时准备两块数据模型mswjs/data的factory声明每个资源的字段、主键、类型作为 handler 里的查询入口。测试数据生成器ngneat/falso为每个模型产出一份默认填充好的随机对象支持字段覆盖便于快速造数据。本文以apps/react-vite为例把这条路径串成可执行流程定义模型 → 生成数据 → 让 handler 消费 → 启动 MSW → 验证数据确实进入 mock API。1. 依赖清单项目中已就位apps/react-vite/package.json中与本任务直接相关的依赖msw^2.2.14拦截器本体mswjs/data^0.16.1模型工厂mswjs/http-middleware^0.10.1把同一批 handler 挂到 Express用于 E2E mock servernanoid^5.0.7模型主键生成器ngneat/falso^7.2.0随机数据生成器在apps/react-vite下执行yarn install后这些包即可直接引入使用。2. 定义数据模型db.ts模型定义集中在 db.ts。核心是mswjs/data的factory与primaryKeyimport { factory, primaryKey } from mswjs/data; import { nanoid } from nanoid; const models { user: { id: primaryKey(nanoid), firstName: String, lastName: String, email: String, password: String, teamId: String, role: String, bio: String, createdAt: Date.now, }, team: { id: primaryKey(nanoid), name: String, description: String, createdAt: Date.now, }, discussion: { id: primaryKey(nanoid), title: String, body: String, authorId: String, teamId: String, createdAt: Date.now, }, comment: { id: primaryKey(nanoid), body: String, authorId: String, discussionId: String, createdAt: Date.now, }, }; export const db factory(models); export type Model keyof typeof models;字段约定id: primaryKey(nanoid)—— 主键由nanoid()生成其余字段以String、Date.now等标注mswjs/data据此做类型校验与默认值。export type Model keyof typeof models—— 后续persistDb按模型名持久化时使用这个联合类型。2.1 持久化与初始化同一文件还导出四个函数handler 与测试都依赖它们函数行为loadDb()Node.js 环境读mocked-db.json不存在时写空{}浏览器环境读localStorage[msw-db]storeDb(data)把 JSON 写回对应位置persistDb(model: Model)把指定模型当前内存中全部行db[model].getAll()合并进持久化数据NODE_ENV test时直接跳过测试不写盘initializeDb()把持久化数据逐条model.create(entry)灌回dbresetDb()仅window.localStorage.clear()即只清理浏览器/测试环境的存储3. 生成测试数据data-generators.tsdata-generators.ts 用ngneat/falso为每个模型提供一个默认填充函数并对外导出一个带overrides的工厂import { randCompanyName, randUserName, randEmail, randParagraph, randUuid, randPassword, randCatchPhrase, } from ngneat/falso; const generateUser () ({ id: randUuid() Math.random(), firstName: randUserName({ withAccents: false }), lastName: randUserName({ withAccents: false }), email: randEmail(), password: randPassword(), teamId: randUuid(), teamName: randCompanyName(), role: ADMIN, bio: randParagraph(), createdAt: Date.now(), }); export const createUser T extends PartialReturnTypetypeof generateUser( overrides?: T, ) { return { ...generateUser(), ...overrides }; };文件里依次导出createUser、createTeam、createDiscussion、createComment。用法固定为默认 覆盖import { createUser, createDiscussion } from /testing/data-generators; const user createUser({ email: seedexample.com }); // { id: …, email: seedexample.com, firstName: …, ... }字段名与db.ts中的模型字段一一对应生成出的对象可以直接交给db.user.create(...)写入。4. 让 handler 消费模型与生成数据Handler 位于 handlers/ 目录按资源拆分为auth.ts/users.ts/teams.ts/discussions.ts/comments.ts在 index.ts 中合并为统一数组export const handlers [ ...authHandlers, ...commentsHandlers, ...discussionsHandlers, ...teamsHandlers, ...usersHandlers, http.get(${env.API_URL}/healthcheck, async () { await networkDelay(); return HttpResponse.json({ ok: true }); }), ];以 discussions.ts 为例handler 通过db直接读写模型import { db, persistDb } from ../db; // 查询按 teamId 过滤并分页 const total db.discussion.count({ where: { teamId: { equals: user?.teamId } } }); const result db.discussion.findMany({ where: { teamId: { equals: user?.teamId } }, take: 10, skip: 10 * (page - 1), }); // 写入请求体合并当前登录用户 const data (await request.json()) as DiscussionBody; const created db.discussion.create({ teamId: user?.teamId, authorId: user?.id, ...data, }); await persistDb(discussion); return HttpResponse.json(created);写操作后统一调用persistDb(modelName)落盘认证辅助函数requireAuth/requireAdmin/authenticate与networkDelay测试 200 ms、开发 300–1000 ms 随机都放在 utils.ts。新增一个模型的完整路径在db.ts的models里加字段 → 在data-generators.ts里加createXxx→ 在handlers/下新建一个文件并导出xxxHandlers最后到handlers/index.ts中展开。5. 启动 MSW三种入口同一批handlers通过不同入口挂到三种运行环境。5.1 浏览器开发模式Vite dev servermocks/index.ts 提供enableMocking()import { env } from /config/env; export const enableMocking async () { if (env.ENABLE_API_MOCKING) { const { worker } await import(./browser); const { initializeDb } await import(./db); await initializeDb(); return worker.start(); } };browser.ts 用setupWorker(...handlers)创建 service worker。package.json中msw: { workerDirectory: public }即 worker 文件放在apps/react-vite/public/mockServiceWorker.js仓库中已存在。需要设置VITE_APP_ENABLE_API_MOCKINGtrueenv.ts 会剥掉VITE_APP_前缀且VITE_APP_API_URL必填。5.2 Vitest 集成测试setup-tests.ts 用setupServerserver.ts在 Node 里起 MSWbeforeAll(() server.listen({ onUnhandledRequest: error })); afterAll(() server.close()); beforeEach(() { // 打桩 ResizeObserver / btoa / atob 后 initializeDb(); }); afterEach(() { server.resetHandlers(); resetDb(); });onUnhandledRequest: error请求到未注册的路径会直接失败便于发现漏写 handler。测试环境persistDb被跳过数据只存活于内存dbresetDb()清localStorage即可重置。5.3 E2E mock serverPlaywrightmock-server.ts 用mswjs/http-middleware把同一批 handler 挂到 Expressapp.use(cors({ origin: env.APP_URL, credentials: true })); app.use(express.json()); app.use(createMiddleware(...handlers)); initializeDb().then(() { app.listen(env.APP_MOCK_API_PORT, () { console.log(Mock API server started at http://localhost:${env.APP_MOCK_API_PORT}); }); });package.json中run-mock-server: vite-node mock-server.ts | pino-pretty -c, test-e2e: pm2 start \yarn run-mock-server\ --name server yarn playwright test默认端口APP_MOCK_API_PORT8080test-e2e会先用 pm2 起 mock server再跑 Playwright。6. 验证数据确实进入 mock API6.1 冒烟healthcheck任何入口下GET ${API_URL}/healthcheck都会经过networkDelay并返回{ ok: true }是确认 MSW 已挂上的最小探针。6.2 造一条 user 再回读以浏览器开发模式为例打开http://localhost:3000确认VITE_APP_ENABLE_API_MOCKINGtrue已生效。发一次POST ${API_URL}/auth/registerbody 使用createUser()的默认字段firstName、lastName、email、password、teamName。auth.ts 的 register handler 会先按 email 去重再db.user.create并persistDb(user)最后写Set-Cookie并返回{ user, jwt }。DevTools → Application → Local Storage 查看msw-db应能看到新增的user数组且password是 utils.ts 中hash()处理后的字符串。再请求GET ${API_URL}/auth/me带 cookie返回{ data: user }其中user已由sanitizeUser去掉password与iat字段。6.3 测试里生成并落库Vitest 中可以直接使用生成器import { db } from /testing/mocks/db; import { createUser } from /testing/data-generators; const payload createUser({ email: seedexample.com }); db.user.create(payload); const found db.user.findFirst({ where: { email: { equals: payload.email } } }); expect(found?.email).toBe(seedexample.com);因为测试环境persistDb被跳过、resetDb()在afterEach里清localStorage这条数据不会影响下一条用例。7. 限制与排查要点VITE_APP_API_URL必填env.ts 里API_URL: z.string()缺失或非法时启动即抛Invalid env provided。ENABLE_API_MOCKING只接受字符串true/false其他取值会让 env 校验直接失败。resetDb()只清window.localStorageNode.js 环境E2E mock server使用的mocked-db.json不会被resetDb()清理需要自行处理该文件以重置状态。persistDb在测试环境跳过这是 setup-tests.ts 能靠resetDb()做 per-test 隔离的前提。onUnhandledRequest: errorVitest 里请求未注册路径会直接失败遇到 unhandled request 报错先补 handler 再跑。networkDelay在测试环境固定 200 ms开发环境 300–1000 ms 随机utils.ts测试等待时间因此稳定开发体验更贴近真实网络抖动。下一步需要切到真实 API 时把VITE_APP_ENABLE_API_MOCKING置为false并配置真实VITE_APP_API_URLenableMocking()就不会再启动 worker模型与 handler 保留下来也可以直接作为后端 API 契约的参考。【免费下载链接】bulletproof-react️ ⚛️ A simple, scalable, and powerful architecture for building production ready React applications.项目地址: https://gitcode.com/GitHub_Trending/bu/bulletproof-react创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考