别再手敲 JSON 造数据了!Mock 数据生成器实战与避坑指南

前几天,群里有朋友问我:"你们前后端并行开发的时候,接口还没好,前端那几十条测试数据是怎么来的?"

说实话,我之前也被这个问题坑过不少次。

最夸张的一次:一个订单列表页,我手写了 200 条 JSON 数据。写到第 80 条的时候后端说字段名要从 orderNo 改成 order_no,改完又说要加个 refundAt,再改完又说要支持分页。

一份"临时用一下"的假数据,我前后维护了大半天。

那一刻我才意识到:手写 Mock 数据不是慢的问题,是它会持续偷走你的时间。

问题根源:手写 Mock 数据的三宗罪

手写 JSON 看着简单,实际上埋了三类坑。

🕳️ 罪一:假得太干净

手敲的数据永远是 "name": "test1"、"remark": "aaa"、"amount": 100。

结果就是:所有边界情况都测不出来。空字符串会不会崩?null 怎么处理?字段超长 200 字符会不会撑破布局?带 emoji 的商品名会不会乱码?金额为 0 和为负的时候页面长什么样?

这些 Bug 全都安安静静地活到了测试环境,甚至活到了线上。

🕳️ 罪二:结构漂移

后端改了字段,没人会记得去同步那份手写的 mock.json。

于是前端拿着过期的数据结构自测,一路绿灯,联调当天集体翻车。

🕳️ 罪三:不可复现

随手写的随机数据,出 Bug 了想复现------数据已经变了。

"你刚才那个报错还在吗?" "我刷新了一下,没了。"

这种对话,做过前端的都懂。

核心结论:Mock 数据的价值不在"有数据",而在"数据结构可控、内容可复现、边界可覆盖"。

Mock 数据的四个层次

我把常见做法分成四层,越往上越接近工程化。

层次 做法 适用阶段 主要问题
L1 静态手写 直接敲 JSON 文件 原型、5 条以内 假得干净、难维护
L2 模板随机 faker / Mock.js 批量生成 联调、demo 结构与接口无契约
L3 Schema 驱动 JSON Schema → 数据 正式项目 需要先有 Schema
L4 场景化 Mock MSW / Mock Server / 录制回放 单测、E2E、长期维护 有一定接入成本

大多数团队卡在 L1 和 L2 之间,而真正省事的是 L3:契约即数据源。接口 Schema 一改,Mock 数据自动跟着变,结构漂移的问题从根上消失。

方案对比:该用哪个

方案 类型 优势 局限
@faker-js/faker JS 库 数据真实、支持中文、可设种子 需要写代码
Mock.js JS 库 模板语法直观、前端拦截请求 语法私有、生态偏老
json-schema-faker JS 库 Schema 驱动、契约一致 依赖已有 Schema
MSW 拦截层 单测/浏览器通吃、贴近真实请求 有接入成本
在线生成器 Web 工具 零安装、秒出结果 只能造扁平结构
数据库造数脚本 SQL/Python 能造关联数据、贴近真实分布 最重

没有银弹。临时用 → 在线工具或 faker;正式项目 → Schema 驱动 + MSW。

实战一:@faker-js/faker(推荐日常主力)

Faker 是目前 JS 生态里最活跃的选择(本文用的是 v10.6.0)。v9 之后 API 做过一轮规整,address 改名 location、name 改名 person,中文 locale 也够用。

bash 复制代码
npm i -D @faker-js/faker
js 复制代码
import { fakerZH_CN as faker } from '@faker-js/faker';

// 关键:设种子,保证每次生成的数据完全一致,Bug 可复现
faker.seed(20260914);

// 中文 locale 下 phone.number() 给的是固话格式(如 0747-95502468),
// 不是 11 位手机号。要手机号得自己拼号段:
const SEGMENTS = ['133', '150', '176', '186', '199'];
const mobile = () => faker.helpers.arrayElement(SEGMENTS) + faker.string.numeric(8);

const orders = Array.from({ length: 200 }, (_, i) => ({
  id: faker.string.uuid(),
  orderNo: `SO${String(i + 1).padStart(8, '0')}`,
  userName: faker.person.fullName(),
  phone: mobile(),
  // 金额用「分」存整数,别用浮点,0.1 + 0.2 的坑不要踩第二次
  amountCents: faker.number.int({ min: 0, max: 5_000_000 }),
  status: faker.helpers.arrayElement(['pending', 'paid', 'shipped', 'refunded']),
  remark: faker.helpers.arrayElement([null, '', faker.lorem.sentence(), '备注含 emoji 🎉']),
  createdAt: faker.date.recent({ days: 30 }).toISOString(),
}));

console.log(JSON.stringify(orders, null, 2));

⚠️ 上面这段我实际跑过(Faker v10.6.0)。fakerZH_CN.phone.number() 输出的是 0747-95502468、14917446540 这种混合结果,拿去做手机号校验的接口会被直接打回。这是个很容易忽略的细节------大家默认中文 locale 就该出中文手机号,其实不是。

⚠️ 注意 remark 那行------我故意把 null、空串、正常文本、emoji 混在一起。这才是 Mock 数据该有的样子:主动制造脏数据。

常用 Faker 片段速查(v9+,以下均实测可运行)

js 复制代码
faker.seed(20260914);                              // 固定种子,必须放最前面
faker.person.fullName();                           // 中文姓名
faker.internet.email();                            // 邮箱
faker.location.city();                             // 城市
faker.string.uuid();                               // UUID
faker.number.int({ min: 0, max: 5_000_000 });      // 区间整数
faker.number.float({ min: 0, max: 100, multipleOf: 0.01 }); // 保留两位小数
faker.date.between({ from: '2026-08-01', to: '2026-09-14' });
faker.helpers.arrayElement(['active', 'frozen']);  // 枚举随机
faker.helpers.multiple(() => ({ /* ... */ }), { count: 200 }); // 批量生成
faker.string.numeric(8);                           // 8 位数字串(拼手机号用)

⚠️ fakerZH_CN.phone.number() 不要直接当手机号用,详见下一节。

实战二:Mock.js(模板语法最直观)

如果你的团队已经在用 Mock.js,它的模板语法确实写得快。

js 复制代码
const Mock = require('mockjs');

const data = Mock.mock({
  'list|50': [{
    'id|+1': 1,
    name: '@cname',
    'age|18-60': 1,
    'level|1': ['VIP1', 'VIP2', 'VIP3'],
    email: '@email',
    'amount|1-10000.2': 1,
    createdAt: '@datetime("yyyy-MM-dd HH:mm:ss")',
    avatar: '@image("80x80", "#4A90E2", "#FFF", "png", "U")',
  }],
});

'list|50' 表示生成 50 条,'id|+1' 表示自增,'level|1' 表示从数组里随机取一个。

⚠️ 这里有个我实测踩到的坑 :@datetime 生成的时间跨度是 1970 年到现在。我跑了 400 次统计,只有 9.5% 的日期落在 2020 年之后------也就是说你造 100 条"最近订单",会有 90 条的时间是 1988 年、2003 年这种。

页面上按时间倒序一排,全是十几年前的订单,一眼就假。而且如果业务逻辑依赖"近 7 天",这些数据直接测不到东西。

正确写法是指定区间:

js 复制代码
// 用 Mock.Random.date 限定范围,别用 @datetime
const d = Mock.Random.date('2026-08-01', '2026-09-14');   // 2026-08-01

局限也很明显:语法是私有的,Schema 换不过来,而且它默认会拦截 XHR------上线前一定要确认 mock 代码没被打进生产包。

另外 @cname、@email 这类占位符生成的是"格式对但内容假"的值(比如 r.pordsdc@vdemckts.name),域名叫 vdemckts.name,一看就不是真邮箱。做前端展示没问题,但如果你的测试逻辑会真的发信或做域名白名单校验,就会炸。

实战三:json-schema-faker(Schema 驱动,最工程化)

这是我最推荐的正式项目做法。前提是你有接口的 JSON Schema(没有的话,可以让后端从 DTO 生成,或者用工具从样例 JSON 反推)。

⚠️ 先说一个会直接让你卡住的问题:网上的教程大多过时了。

搜到的写法基本都是 const { JSONSchemaFaker } = require('json-schema-faker'),然后 JSONSchemaFaker.generate(schema)。我在 v0.6.3 上实测,两行都跑不通:

  • require() 直接抛 ERR_PACKAGE_PATH_NOT_EXPORTED------新版本只发 ESM ,exports 字段里只有 import,没有 require
  • 没有 JSONSchemaFaker 这个命名导出了,改成了一组扁平函数:generate / generateSync / registerFormat / define 等

正确写法:

js 复制代码
// 注意:ESM,且是 generateSync 不是 JSONSchemaFaker.generate
import { generateSync } from 'json-schema-faker';

const schema = {
  type: 'object',
  required: ['orderId', 'amount', 'status'],
  properties: {
    orderId: { type: 'string', format: 'uuid' },
    amount: { type: 'integer', minimum: 0, maximum: 5000000 },
    status: { type: 'string', enum: ['pending', 'paid', 'shipped', 'refunded'] },
    couponId: { type: ['string', 'null'] },
    paidAt: { type: ['string', 'null'], format: 'date-time' },
  },
};

const order = generateSync(schema);
// => {"orderId":"a08ff49b-6f77-4263-8716-c43069c43a8c","amount":964070,
//     "status":"shipped","couponId":null,"paidAt":"2010-05-08T03:43:34Z"}

const orders = Array.from({ length: 200 }, () => generateSync(schema));

如果你的项目还是 CommonJS,只能锁老版本(json-schema-faker@0.5.x)或者改成 ESM。这个坑我踩了小半天,因为报错信息 No "exports" main defined 完全没提示你是模块格式问题。

好处:Schema 是唯一事实来源。后端改字段 → Schema 变 → Mock 自动变,结构漂移彻底消失。同一份 Schema 还能拿去做接口文档和响应校验,一份投入三处收益。

🎯 顺带一提,从上面那段真实输出能看出 Schema 驱动的价值:couponId 自动生成了 null,amount 自动落在 minimum/maximum 区间内,status 自动从 enum 里取值。这些约束条件写在 Schema 里,生成的数据天然就是合法的,不用像手写那样靠人脑记"这个字段可以为空"。

反过来也要注意:paidAt 生成的是 2010-05-08,因为 Schema 里没写时间范围。Schema 只保证"格式合法",不保证"业务合理" ------需要近期时间的话,得自己加 format 扩展或后处理。

实战四:在线生成器(临时用,别当主力)

如果只是要一批扁平结构的数据,贴进 Postman 或者塞进 demo 页面,我一般懒得开编辑器写脚本。

我自己用的是这个纯前端的小工具:Mock 数据生成器。用法很直白------配字段名和类型,填行数,点生成,复制或下载 JSON。支持的类型有 name、email、phone、address、date、uuid、url、ip、number、boolean、custom。

https://leowh.com/tools/mock-data/index.html

两个细节我觉得设计得挺对:

  • custom 支持 值A|值B|值C 这种竖线分隔的候选文本,枚举字段直接搞定
  • number 可以设最小值和最大值,区间数据不用自己算

它是纯前端实现,配置和数据都在浏览器本地生成,不往服务器发------对内网环境或者字段本身敏感的场景友好一些。

但边界要讲清楚,别拿它当主力:

  • 🔴 只能造扁平对象数组,不支持嵌套对象、外键关联、条件依赖(比如 status = refunded 时必须有 refundAt)
  • 🔴 每次生成都是随机的,没有种子机制,Bug 不可复现
  • 🟡 造完就是一坨静态 JSON,接口字段一改又得重来一遍

所以我的分工是:demo 和联调冒烟用在线工具,正式测试数据和单测夹具回到代码里写(faker / Schema 驱动)。

顺带一提,同一站还有个 JSON 转实体类工具,把生成好的 JSON 贴进去能直接出 Java / C# / Go / TypeScript / Python 的类定义。造数据 → 看结构 → 生成实体,在接口文档还没写全的阶段,这条链路能省点事。

实战五:MSW(让单测吃到真实请求链路)

到了 L4,重点是"拦截"而不是"生成"。MSW(Mock Service Worker)在 Service Worker 层拦请求,单测和浏览器调试用同一套 handler。

bash 复制代码
npm i -D msw
js 复制代码
// mocks/handlers.js
import { http, HttpResponse } from 'msw';
import { fakerZH_CN as faker } from '@faker-js/faker';

faker.seed(42);

const makeOrders = (n) =>
  Array.from({ length: n }, () => ({
    id: faker.string.uuid(),
    amountCents: faker.number.int({ min: 0, max: 5_000_000 }),
    status: faker.helpers.arrayElement(['pending', 'paid', 'shipped', 'refunded']),
  }));

export const handlers = [
  http.get('/api/orders', ({ request }) => {
    const url = new URL(request.url);
    const page = Number(url.searchParams.get('page') ?? 1);
    return HttpResponse.json({ page, total: 200, list: makeOrders(20) });
  }),

  // 主动造异常,这才是 Mock 最大的价值
  http.get('/api/orders/:id', () =>
    HttpResponse.json({ code: 500, msg: 'internal error' }, { status: 500 })),
];

🎯 重点:Mock 不只是造正常数据,更是造异常。 500、超时、限流 429、字段缺失、字段类型错误------这些场景在真实环境里很难稳定复现,但在 Mock 层里一行代码的事。

避坑清单

这部分是我踩过的坑,按优先级排。

🔴 必须注意

  1. 一定要设随机种子。 faker.seed(n) 写死,Bug 才能复现。不设种子的随机数据等于没数据。
  2. 金额别用浮点。 用整数「分」或者 decimal 字符串。amount: 19.99 这种写法迟早出事。
  3. 主动造脏数据。 每个可空字段都要覆盖 null、undefined、'';字符串字段要覆盖超长(200+ 字符)、emoji、HTML 特殊字符、换行。
  4. Mock 代码不能进生产包。 Mock.js 会拦 XHR,MSW 的 worker 文件要按环境注入。上线前搜一遍构建产物。
  5. 时间字段统一格式。 到底是 ISO 8601(2026-09-14T10:00:00.000Z)、时间戳、还是本地时间字符串?团队内定死一种,Mock 数据必须和后端一致,尤其注意时区。

🟡 建议注意

  1. 敏感数据合规。 手机号、身份证、银行卡一律用生成的虚构值,绝对不要从生产库导真实数据当 Mock。Faker 生成的手机号是合法号段格式但不对应真实用户,仍然建议只在内网/测试环境流转。
  2. 别一次造太多。 前端一次渲染 10 万条 JSON,浏览器先卡死给你看。列表页造 50--500 条足够,压测数据另说。
  3. 关联数据要有外键一致性。 order.userId 必须能在 users 里找到,否则测关联查询全是假阳性。这类需求 faker 也能做,但要自己维护 ID 池。
  4. 枚举值从后端常量同步。 手写 ['pending', 'paid'] 很容易漏掉后端新增的状态,最好从共享的常量文件或 Schema 的 enum 读。
  5. 数据分布要像真的。 全是 status: 'paid' 的订单列表,测不出状态筛选逻辑。按真实比例分配(比如 60% paid、20% pending、15% shipped、5% refunded)。

什么情况下用哪种方案?

🔴 直接上 Schema 驱动(json-schema-faker + MSW)

  • 项目会长期维护,多人协作
  • 已经有或愿意维护接口 JSON Schema
  • 需要写单元测试 / E2E 测试
  • 接口字段变动频繁

🟡 用 faker / Mock.js 写脚本

  • 项目中等规模,没有 Schema
  • 需要造有一定业务逻辑的数据(关联、分布、条件依赖)
  • 数据需要可复现(配合种子)

🟢 用在线生成器

  • 一次性 demo、临时联调、教学示例
  • 扁平结构、几百条以内
  • 不想装依赖、不想写脚本

常见问题

Q:Mock 数据和后端真实数据不一致怎么办?

A:根源是没有单一事实来源。最好的解法是 Schema 驱动------后端 DTO 生成 Schema,前端从 Schema 生成 Mock,同时用 Schema 做响应校验。退一步的做法是引入契约测试(如 Pact),至少能在 CI 里发现漂移。

Q:为什么我造的数据测不出 Bug,一到测试环境就炸?

A:因为你的数据太干净了。检查三件事:有没有覆盖 null / 空串 / 字段缺失?有没有超长字符串和特殊字符?有没有造过异常响应(500 / 超时 / 429)?

Q:随机种子设多少合适?

A:随便,但要写死并记在代码注释里,比如 faker.seed(20260914)。需要复现某个 Bug 时,把当时用的种子固定下来,写成一个测试用例。

Q:能用生产库的数据脱敏后当 Mock 吗?

A:技术上可以,但成本高且风险大------脱敏不彻底就是数据泄露事故。除非你在做「数据分布真实性」要求极高的场景(比如推荐算法),否则用生成式数据更安全。

Q:Mock.js 和 MSW 能一起用吗?

A:不建议。两者都会拦请求,容易互相干扰导致排查困难。选一个,团队统一。

Q:在线生成器造的数据能直接用于自动化测试吗?

A:不适合。它没有种子机制,每次生成结果不同,测试会变成 flaky test。自动化测试的数据必须由代码生成并固定种子。

总结

Mock 数据这件事,本质是从「手工作坊」升级到「流水线」:

✅ L1 → L2 :别手敲 JSON 了,用 faker / Mock.js 批量生成,效率提升一个数量级

✅ L2 → L3 :引入 JSON Schema,让契约成为唯一事实来源,根治结构漂移

✅ L3 → L4 :用 MSW 统一拦截层,让单测和联调共用一套 Mock,并且开始主动造异常

✅ 贯穿始终:设种子保证可复现、主动造脏数据覆盖边界、金额用整数、Mock 不进生产包

⚠️ 特别提醒: 如果你的 Mock 数据永远只有"正常值",那它测出来的"通过"没有任何意义。

工具选型上,我的习惯是分场景:正式项目走 Schema 驱动 + faker,临时 demo 和联调冒烟用在线生成器图个快。两者不是替代关系。

相关推荐
EVE จุ๊บ7 小时前
playwright和selenium的区别是什么
selenium·测试工具
Meta Luo2 天前
页面改版后自动化用例大量失败,应该怎么维护?
运维·功能测试·测试工具·自动化
释槐鸟~2 天前
还不知道RIDE工具怎么装吗
测试工具·自动化
有技巧搬砖3 天前
为什么锂电池组的电池老化测试很重要?
测试工具·锂电池·电池老化
蒲公英内测分发3 天前
智能手表卖出去后,包装、说明书和官网里的 App 下载入口怎么统一?
前端·人工智能·测试工具·智能家居·智能硬件·智能手表
蒲公英内测分发3 天前
智能手环出海测试,海外合作方分散在不同国家,Android 测试包怎么持续分发和更新?
android·前端·人工智能·测试工具·智能手表
无语的小歪歪3 天前
ATF 智测 1.0
python·测试工具·集成测试
SHENGWEI胜为4 天前
交换机端口显示正常,终端却获取不到 IP 地址?从 DHCP 流程定位故障
服务器·网络·测试工具·缓存·通信工程
xie881018024 天前
长表格跨页重复表头:排版上的做法
测试工具
老孟说禅4 天前
测试工具示波器的使用
测试工具