@pylonts/mock
v1.1.5
Published
TypeScript 语义化测试数据生成器。提供带业务规则的随机数据生成函数,而非纯随机字符串。
Readme
@pylonts/mock — Mock Data Generators
TypeScript 语义化测试数据生成器。提供带业务规则的随机数据生成函数,而非纯随机字符串。
Philosophy
三级精度
| 级别 | 含义 | 例子 |
|------|------|------|
| 合法 | 格式正确,能通过基本校验 | mockNumbers(6) → "834102" |
| 合理 | 业务规则下有意义 | mockPhone() → "13812345678"(手机号段正确) |
| 可关联 | 跨实体 ID 一致,可组成有效请求 | mockMerchantId() → "M12345678"(兼容 DB 主键) |
本库保证所有生成器至少达到 合理 级别。纯随机字符串(mockAbc / mockNumbers)作为基础构件暴露给调用方自行组合。
语义生成器 ≠ 纯随机
纯随机: Math.random().toString(36).slice(2, 8)
语义生成器: mockPhone() → "13812345678" (合规号段)
mockIdCard() → "110101199001011234" (通过校验位)
mockBankCard() → "6222021234567890" (真实 BIN 前缀)
mockCompanyName() → "深圳市创新科技有限公司" (真实地名+行业用字)什么字段该进库
进入 @pylonts/mock 的唯一前提:生成正确值需要非平凡的业务领域规则。
| 字段 | 规则 | 进库? |
|------|------|--------|
| phone | 号段前缀选择(13x/15x/17x/18x/19x) | ✅ 已有 |
| idCard | 地区码+日期+顺序码+校验位算法 | ✅ 已有 |
| bankCard | 真实 BIN 前缀 + Luhn 校验 | ✅ 已有 |
| companyName | 真实地名+字号+行业后缀 | ✅ 已有 |
| amt | 两位小数,正数,单位元 | ✅ 已有 |
| rate | 四位小数以内,范围 0.001-0.030 | ✅ 已有 |
| name | 姓氏库+频次统计 | ✅ 已有 |
| datetime | 就是一段有效日期 | ❌ new Date() 即可 |
| description | 无规则,纯填充 | ❌ mockChinese() 即可 |
反例:datetime 不该进库
// ❌ 错误做法:试图用 mockInteger 组合 datetime
const mockDatetime = () => {
const y = mockInteger(2024, 2026);
const m = String(mockInteger(1, 12)).padStart(2, '0');
const d = String(mockInteger(1, 28)).padStart(2, '0');
const hh = String(mockInteger(0, 23)).padStart(2, '0');
const mm = String(mockInteger(0, 59)).padStart(2, '0');
const ss = String(mockInteger(0, 59)).padStart(2, '0');
return `${y}-${m}-${d} ${hh}:${mm}:${ss}`;
};各段独立随机,导致日期不存在(2025-02-29、2024-04-31),固定 1-28 又丢失了合法日期。
正确做法:项目层一行 new Date(),不需进库。
// ✅ 项目层单行搞定
const now = new Date();
const paidAt = new Date(now.getTime() - mockInteger(3600000, 86400000)); // 1-24小时前生成器准入规则
对于确定要进库的生成器,必须满足:输出永远符合所属数据类型的全部业务规则,而不是仅仅符合正则。
| 维度 | 规则 | 反例 |
|------|------|------|
| 手机号 | 号段合规(移动/联通/电信可识别),11 位 | Numbers(11) → 号段可能为 "000" |
| 身份证 | 18 位,地区码+日期+顺序码+校验位全部正确 | Numbers(18) → 校验位大概率错误 |
| 银行卡 | 使用真实 BIN 前缀,通过 Luhn 校验 | Numbers(16) → BIN 不存在,Luhn 不通过 |
| 金额 | 两位小数,正数,单位元 | Number() → 可能为 3.14159 |
| 费率 | 四位小数以内,范围 0.001-0.030 | Math.random() → 可能为 0.5 |
| 公司名 | 真实地名+字号+行业后缀 | Chinese(8) → "鞷閏鬻龘有限公司" |
组合优先,新增其次
能用现有生成器组合出的语义,暂不新增生成器,接入方在外层组合即可:
VERIFY_CODE = mockNumbers(6) ✅ 直接复用
RECON_PERIOD = `2026-${pad(mockInteger(1,12))}` ✅ 表达式组合
USCC = mockAbcUpper(1) + mockNumbers(17) ⚠️ 暂代,需校验位时再升级判断标准:当同一个组合逻辑出现在 2 个以上消费方时,就应该收归库内实现。
Architecture
src/
├── index.ts # re-export all generators
├── types.ts # type aliases for generator signatures
├── generators/
│ ├── name.ts # mockName, mockChinese
│ ├── phone.ts # mockPhone
│ ├── finance.ts # mockAmt, mockRate, mockUnionpayId
│ ├── company.ts # mockCompanyName, mockAddress, mockCompanyInfo
│ ├── id.ts # mockMerchantId, mockOrderId, mockSeq
│ ├── identity.ts # mockIdCard, mockBankCard
│ └── random.ts # mockAbc, mockAbcUpper, mockAbc123, mockNumbers, mockInteger, mockBirthday分层
random.ts ── 基础构件(纯随机字符串/数字,所有其他生成器的基石)
name.ts/phone.ts ── 业务基础(有业务规则的单一数据类型)
company.ts ── 复合业务(组合多个基础生成器,返回结构化数据)
id.ts ── 实体 ID(有唯一性要求的生成器)
identity.ts ── 合规数据(有校验位/BIN 算法的生成器)生成器契约
所有生成器遵循统一签名:(...args?) => T,无状态(mockSeq 除外,它有自增计数器)。
// 无参数
type PhoneGenerator = (operator?: Operator) => string;
// 可选参数约束范围
type AmountGenerator = (min?: number, max?: number) => string;
// 固定返回
type RateGenerator = () => string;Usage
import {
mockName, mockPhone, mockAmt, mockRate,
mockCompanyName, mockAddress,
mockMerchantId, mockOrderId, mockSeq,
mockIdCard, mockBankCard,
mockAbc, mockNumbers, mockInteger, mockBirthday,
} from '@pylonts/mock';
// 基本用法
mockName(); // "张伟"
mockName('female'); // "李娜"
mockPhone(); // "13812345678"
mockPhone('unicom'); // "18612345678"
mockAmt(10, 100); // "58.50"
mockRate(); // "0.006"
mockMerchantId(); // "M1234567890123"Extending
原则
- 先组合,再新增:
mockNumbers(6)可以满足的场景,不写mockVerifyCode() - 合理是底线:新增生成器必须保证输出符合业务规则(如手机号对号段、身份证对校验位)
- 无状态优先:除
mockSeq外,生成器不应有副作用或内部状态
方式 A:组合现有生成器(推荐)
在项目层组合,不修改本库。项目自定义 mock 分两步:定义 descriptor 工厂 + 注册 resolver。
// schema/_mock.ts — project-level descriptor factories
import type { MockDescriptor } from '@pylonts/dsl';
import { mockNumbers, mockInteger, mockCompanyName, mockName, mockAbc123 } from '@pylonts/mock';
export const verifyCode = (): MockDescriptor => ({ fn: 'verifyCode' });
export const period = (): MockDescriptor => ({ fn: 'period' });
export const advertColor = (): MockDescriptor => ({ fn: 'advertColor' });
export const storeName = (): MockDescriptor => ({ fn: 'storeName' });
export const couponName = (): MockDescriptor => ({ fn: 'couponName' });// test setup — register resolvers
import { createBuiltinRegistry } from '@pylonts/mock';
import { verifyCode, period, advertColor, storeName, couponName } from './schema/_mock';
const reg = createBuiltinRegistry();
reg.register('verifyCode', () => () => mockNumbers(6));
reg.register('period', () => () => `2026-${String(mockInteger(1, 12)).padStart(2, '0')}`);
reg.register('advertColor', () => () => `#${mockAbc123(6).toUpperCase()}`);
reg.register('storeName', () => () => `${mockName(1)}路店`);
reg.register('couponName', () => () => `${mockCompanyName()}专属套餐`);
// resolve
reg.resolve(verifyCode()); // → "483921"方式 B:新增基础生成器
当组合无法满足业务规则时(如 USCC 有校验位算法):
- 在
src/generators/下新建文件或追加到现有文件 - 遵循
(...args?) => T签名 - 在
types.ts添加类型别名(可选,仅当签名是新的模式时) - 在
index.ts添加 re-export
// src/generators/finance.ts
export function mockUscc(): string {
const prefix = '91'; // 深圳地区代码
const body = mockNumbers(8);
const checkDigit = calculateCheckDigit(prefix + body);
return prefix + body + checkDigit;
}方式 C:业务派生
基于本库生成器,产出带业务上下文的复合数据,也分 descriptor 工厂和 resolver 注册两步:
// schema/_mock.ts(项目层)
import type { MockDescriptor } from '@pylonts/dsl';
export const merchantRow = (): MockDescriptor => ({ fn: 'merchantRow' });// test setup
import { createBuiltinRegistry, mockCompanyName, mockName, mockPhone, mockAddress } from '@pylonts/mock';
import { merchantRow } from './schema/_mock';
const reg = createBuiltinRegistry();
reg.register('merchantRow', () => () => ({
name: mockCompanyName(),
contactName: mockName(),
contactPhone: mockPhone(),
bizAddress: mockAddress(),
}));
reg.resolve(merchantRow()); // → { name: '...', contactName: '...', ... }与语义的集成
语义编码 → 生成器的映射不在本库中维护,由项目层负责:项目在 schema/_mock.ts 定义 descriptor 工厂,注册 resolver 进 MockRegistry 后按 descriptor.fn 解析:
// schema/_mock.ts(项目层)
import type { MockDescriptor } from '@pylonts/dsl';
export const merchantName = (): MockDescriptor => ({ fn: 'merchantName' });
export const bdName = (): MockDescriptor => ({ fn: 'bdName' });
export const contactPhone = (): MockDescriptor => ({ fn: 'contactPhone' });// test setup
import { createBuiltinRegistry, mockCompanyName, mockName, mockPhone } from '@pylonts/mock';
import { merchantName, bdName, contactPhone } from './schema/_mock';
const registry = createBuiltinRegistry();
registry.register('merchantName', () => mockCompanyName);
registry.register('bdName', () => mockName);
registry.register('contactPhone', () => mockPhone);
// 之后按 descriptor.fn 解析:
registry.resolve({ fn: 'merchantName' }); // → '某某科技有限公司'
registry.resolve(merchantName()); // → 同上当前生成器清单
名称/文本
| 方法 | 签名 | 说明 |
|------|------|------|
| mockName | (gender?, surnameType?) => string | 中文姓名,支持性别/单复姓 |
| mockChinese | (length?) => string | 随机中文字符串(占用 GBK 空间的合法汉字,非生僻字) |
手机号
| 方法 | 签名 | 说明 |
|------|------|------|
| mockPhone | (operator?) => string | 11 位手机号,可选 mobile/unicom/telecom,号段合规 |
财务
| 方法 | 签名 | 说明 |
|------|------|------|
| mockAmt | (min?, max?) => string | 金额,如 "58.50",范围默认 1-1000,两位小数 |
| mockRate | () => string | 费率,如 "0.006",范围 0.001-0.030 |
| mockUnionpayId | () => string | 银联商户编号,9-12 位数字 |
公司/地址
| 方法 | 签名 | 说明 |
|------|------|------|
| mockCompanyName | () => string | 公司全称(城市前缀 + 字号 + 行业后缀),如 "深圳市创新科技有限公司" |
| mockAddress | () => string | 中文地址,省市区 + 街道 |
| mockCompanyInfo | () => [string, string, string] | [全称, 简称, 地址] 三元组,三值保持一致 |
ID/序号
| 方法 | 签名 | 说明 |
|------|------|------|
| mockMerchantId | () => string | 商户 ID,VARCHAR(12) 兼容,M 前缀 + 时间戳片段 + 随机后缀 |
| mockOrderId | () => string | 订单 ID,ORD + 时间戳 + 随机后缀 |
| mockSeq | (prefix?) => string | 自增序号,模块级计数器,每次调用 +1 |
身份信息
| 方法 | 签名 | 说明 |
|------|------|------|
| mockIdCard | () => string | 18 位身份证号,地区码/出生日期/顺序码/校验位全部合规 |
| mockBankCard | () => string | 银行卡号,从真实 BIN 前缀数据生成,通过 Luhn 校验 |
账号/密码
| 方法 | 签名 | 说明 |
|------|------|------|
| mockPassword | () => string | 固定密码,返回 "123456"(descriptor fn: 'password') |
通用随机
| 方法 | 签名 | 说明 |
|------|------|------|
| mockAbc | (count?) => string | 小写字母,默认 5-15 位 |
| mockAbcUpper | (count?) => string | 大写字母,默认 5-15 位 |
| mockAbc123 | (count?) => string | 字母数字混合,默认 5-15 位 |
| mockNumbers | (count?) => string | 纯数字字符串,默认 5-15 位 |
| mockInteger | (min?, max?) => number | 随机整数,默认 0-100000000 |
| mockBirthday | (startYear?, endYear?) => string | 生日,yyyyMMdd 格式,默认 1960-2005 |
License
MIT
