@joxnathan/mock-randomizer
v0.3.0
Published
fills a structure with randomized data to mock a service or other model provider. number of items in a collection of objects can also be random.
Maintainers
Readme
Mock Randomizer
fills a structure with randomized data to mock a service or other model provider. number of items in a collection of objects can also be random.
Installation
npm install @joxnathan/mock-randomizer -D
Usage
JSON DSL v2 is the preferred way to describe generated values. Put a $mock object anywhere a synthetic value should be created, then pass generation controls such as range, nested array ranges, and seed through the options object.
import mock from '@joxnathan/mock-randomizer';
const changeHistoryTemplate = {
Id: { $mock: 'guid', $version: 2 },
TimeStamp: { $mock: 'date', min: 'now', max: 'now' },
User: {
Id: { $mock: 'number', min: 100, max: 501 },
Name: { $mock: 'string', size: 8 },
Email: { $mock: 'email' }
},
Data: [{
Id: { $mock: 'number', min: 1000, max: 2001 },
ChangeType: { $mock: 'choose', choices: ['Insert', 'Update', 'Delete'] },
TableName: { $mock: 'choose', choices: ['Claimant', 'Processor', 'Advisor', 'Supervisor', 'Client', 'Vendor'] },
RecordId: { $mock: 'number', min: 10, max: 41 },
FieldName: { $mock: 'choose', choices: ['Id', 'Name', 'Address', 'City', 'State', 'Zip'] },
PrevValue: '{}',
CurrValue: '{}'
}]
};
const detailTemplate = {
Id: { $mock: 'guid', $version: 2 },
Version: { $mock: 'ver' },
Name: { $mock: 'string' },
Value: { $mock: 'string', minLength: 4, maxLength: 6, case: 'upper' },
FullName: { $mock: 'fullname' },
FirstName: { $mock: 'firstname' },
LastName: { $mock: 'lastname' },
Company: { $mock: 'company' },
Street: { $mock: 'street' },
City: { $mock: 'city' },
State: { $mock: 'state' },
Zip: { $mock: 'zip' },
Tz: { $mock: 'tz' },
Phone: { $mock: 'phone' },
Email: { $mock: 'email' },
Book: { $mock: 'title' },
TypeSet: { $mock: 'typeset' },
Locations: [{ $mock: 'city' }],
HasApproval: { $mock: 'boolean' },
CreatedOn: { $mock: 'date', min: 'today-12M', max: 'now-1M' },
CreatedBy: { $mock: 'string' },
ModifiedOn: { $mock: 'date', min: 'today-7D', max: 'now' },
ModifiedBy: { $mock: 'string' }
};
this.history.GetChangeHistory = () => Promise.resolve(
mock.filledList(changeHistoryTemplate, {
range: { min: 11, max: 75 },
Data: { min: 1, max: 4 },
seed: 'history:change-history:v2:case-001'
}).map(entry => {
const getData = () => JSON.stringify(mock.filledObject(detailTemplate, {
Locations: { min: 1, max: 3 }
}));
for (const data of entry.Data ?? []) {
const vals = {
prev: ['Delete', 'Update'].includes(data.ChangeType) ? getData() : undefined,
curr: ['Insert', 'Update'].includes(data.ChangeType) ? getData() : undefined
};
data.PrevValue = vals.prev;
data.CurrValue = vals.curr;
}
return entry;
})
);RunKit Usage
const mock = require("@joxnathan/mock-randomizer").default;
const records = mock.filledList({
Id: { $mock: 'guid', $version: 2 },
Name: { $mock: 'fullname' },
Status: { $mock: 'choose', choices: ['New', 'Open', 'Closed'] },
CreatedOn: { $mock: 'date', min: 'today-7D', max: 'now' }
}, {
range: { min: 2, max: 4 },
seed: 'runkit:sample:v2'
});
console.log({ records });JSON DSL v2
JSON DSL v2 is the current template language for new work. A JSON instruction is an object with $mock and an optional $version. If $version is omitted, the current JSON DSL version is used. For long-lived templates, snapshots, or database seed files, include $version: 2 so the intended DSL contract is explicit.
const record = mock.filledObject({
Id: { $mock: 'guid', $version: 2 },
Name: { $mock: 'fullname', gender: 'female' },
Status: { $mock: 'choose', choices: ['New', 'Open', 'Closed'] },
Score: { $mock: 'number', min: 80, max: 101, inclusive: true },
Code: { $mock: 'string', minLength: 4, maxLength: 6, case: 'upper' },
CreatedOn: { $mock: 'date', min: 'today', max: 'now' }
});Common v2 instructions include:
{ $mock: 'guid' }
{ $mock: 'number', min: 1, max: 100, inclusive: true }
{ $mock: 'date', min: 'today-7D', max: 'now' }
{ $mock: 'string', minLength: 4, maxLength: 12, case: 'mixed' }
{ $mock: 'choose', choices: ['New', 'Open', 'Closed'] }
{ $mock: 'fullname', gender: 'female' }
{ $mock: 'email' }
{ $mock: 'phone', state: 'AZ' }JSON DSL objects are validated before generation. Unknown fields, unsupported $mock types, invalid enum values, empty choice lists, invalid ranges, and conflicting aliases throw DSLParseError with a stable code and path.
Synthetic Data Layer
The synthetic layer adds preflight validation and reusable dataset definitions on top of JSON DSL templates. Use validateTemplate when you want a report instead of a thrown generation error.
const validation = mock.synthetic.validateTemplate({
User: {
Email: { $mock: 'email', $version: 2 }
},
Orders: [{
Total: { $mock: 'number', min: 10, max: 1 }
}]
});
if (!validation.valid) {
console.log(validation.issues);
// [{ code: 'invalid_range', path: 'Orders[0].Total.max', ... }]
}Use compileTemplate when a template should be checked once and reused many times.
const userTemplate = mock.synthetic.compileTemplate({
Id: { $mock: 'guid', $version: 2 },
Name: { $mock: 'fullname' },
Email: { $mock: 'email' },
Status: { $mock: 'choose', choices: ['New', 'Active', 'Disabled'] }
});
const user = userTemplate.fill({ seed: 'users:v2:case-001' });
const users = userTemplate.fillList({
seed: 'users:v2:list-001',
range: { min: 10, max: 10 }
});Use generateSyntheticData for simple entity/table style datasets. Dataset seeds are expanded per entity so each table has a deterministic but separate generated stream.
const data = mock.synthetic.generateSyntheticData({
seed: 'claims-sandbox:v2',
entities: {
Users: {
template: userTemplate.template,
options: { range: { min: 25, max: 25 } }
},
AuditEvents: {
template: {
Id: { $mock: 'guid', $version: 2 },
EventType: { $mock: 'choose', choices: ['Created', 'Updated', 'Deleted'] },
Actor: { $mock: 'fullname' }
},
options: { range: { min: 100, max: 100 } }
}
}
});Synthetic references let later entities point at records generated by earlier entities. By default, $ref picks a deterministic random record from the target entity, then returns the requested path.
const data = mock.generateSyntheticData({
seed: 'claims-sandbox:v2',
entities: {
Users: {
template: {
Id: { $mock: 'guid', $version: 2 },
Name: { $mock: 'fullname' },
Email: { $mock: 'email' }
},
options: { range: { min: 10, max: 10 } }
},
Claims: {
template: {
Id: { $mock: 'guid', $version: 2 },
AssignedUserId: { $ref: 'Users', path: 'Id' },
AssignedUserEmail: { $ref: 'Users.Email' }
},
options: { range: { min: 50, max: 50 } }
}
}
});Use index when a scenario needs an exact fixture row. Indexes are zero-based and must exist in the generated entity list.
const claim = mock.generateSyntheticData({
seed: 'claims-sandbox:v2:indexed',
entities: {
Users: {
template: {
Id: { $mock: 'guid', $version: 2 },
Name: { $mock: 'fullname' }
},
options: { range: { min: 4, max: 4 } }
},
Claims: {
template: {
ReviewerName: { $ref: 'Users', index: 3, path: 'Name' },
SameReviewerName: { $ref: 'Users[3].Name' }
},
options: { range: { min: 1, max: 1 } }
}
}
}).Claims[0];References only point backward to entities that have already been generated. Missing entities, forward references, empty target lists, out-of-range indexes, and missing paths throw stable synthetic reference errors.
Use scope when multiple fields in the same generated record should come from the same selected related record. The scope is local to each generated root record, so each claim below gets one deterministic assigned user while AssignedUserId, AssignedUserName, and AssignedUserEmail stay consistent with each other.
const data = mock.generateSyntheticData({
seed: 'claims-sandbox:v2:scoped',
entities: {
Users: {
template: {
Id: { $mock: 'guid', $version: 2 },
Name: { $mock: 'fullname' },
Email: { $mock: 'email' }
},
options: { range: { min: 10, max: 10 } }
},
Claims: {
template: {
AssignedUserId: { $ref: 'Users', scope: 'assignedUser', path: 'Id' },
AssignedUserName: { $ref: 'Users', scope: 'assignedUser', path: 'Name' },
AssignedUserEmail: { $ref: 'Users.Email', scope: 'assignedUser' }
},
options: { range: { min: 50, max: 50 } }
}
}
});References that share a scope must target the same entity. They must also either all omit index or all use the same explicit index, so scoped references do not depend on traversal order.
Do not write references with v1-style strings. A value such as "$ref:Users.Id" is rejected during validation. Use JSON object form instead:
{ "$ref": "Users", "path": "Id" }Templates can also be loaded from JSON files. Relative paths are resolved from the current project directory unless baseDir is provided.
const template = await mock.loadTemplate('./templates/user.json');
const compiled = await mock.loadCompiledTemplate('./templates/user.json');
const user = compiled.fill({ seed: 'users:v2:case-001' });Dataset files can reference template files with paths relative to the dataset file.
const data = await mock.loadSyntheticData('./datasets/claims-sandbox.json', {
rootDir: '.'
});Example dataset file:
{
"seed": "claims-sandbox:v2",
"entities": {
"Users": {
"template": "../templates/user.json",
"options": { "range": { "min": 25, "max": 25 } }
},
"AuditEvents": {
"template": {
"Id": { "$mock": "guid", "$version": 2 },
"EventType": { "$mock": "choose", "choices": ["Created", "Updated", "Deleted"] }
},
"options": { "range": { "min": 100, "max": 100 } }
}
}
}Absolute disk paths and UNC paths are supported. Use rootDir to restrict where templates may be loaded from. HTTP(S) template loading is disabled by default; set allowNetwork: true only for trusted sources.
Output Adapters
Output adapters turn generated data into plain strings for tests, snapshots, pipeline artifacts, or handoff to database-specific tooling. They do not write files or connect to databases.
const data = mock.generateSyntheticData({
seed: 'claims-sandbox:v2',
entities: {
Users: {
template: {
Id: { $mock: 'guid', $version: 2 },
Name: { $mock: 'fullname' },
Email: { $mock: 'email' },
CreatedOn: { $mock: 'date', min: 'today', max: 'now' }
},
options: { range: { min: 2, max: 2 } }
}
}
});
const prettyJson = mock.toJson(data);
const compactJson = mock.toJson(data, { pretty: false });
const usersNdjson = mock.toNdjson(data, { entity: 'Users' });
const allNdjson = mock.toNdjson(data);
const usersCsv = mock.toCsv(data, 'Users');JSON output is pretty printed by default and can be made compact with { pretty: false }. Dates are serialized as ISO strings.
NDJSON output can target one entity or the whole dataset. Whole-dataset NDJSON includes the entity name on each line so records from different tables do not lose context.
CSV output targets one entity at a time because each entity can have a different shape. Nested objects and arrays are written as JSON strings inside the cell. Use columns to control field order or project a smaller export.
const csv = mock.toCsv(data, {
entity: 'Users',
columns: ['Id', 'Email', 'CreatedOn'],
delimiter: ','
});When columns is provided, adapters reject a column that is not present in any selected record. Empty record sets also fail by default. Use allowMissingColumns: true or allowEmpty: true only when that shape is intentional.
Database insert payload adapters are also pure string/object builders. They do not open connections, execute SQL, or write files.
Use toInsertRows when a test database client already handles parameterization.
const rows = mock.toInsertRows(data, {
entity: 'Users',
columns: ['Id', 'Email', 'CreatedOn']
});
// example shape for a query builder:
// await db('Users').insert(rows);Dates are normalized to ISO strings. Nested objects and arrays are JSON strings by default so rows stay scalar-friendly. Set nested: 'preserve' when the target client accepts structured JSON column values.
const jsonColumnRows = mock.toInsertRows(data, {
entity: 'Users',
nested: 'preserve'
});Use toSqlInserts for deterministic non-production fixture scripts. Identifiers are validated before SQL is produced, string values are escaped, and table names must be explicit for raw record arrays.
const sql = mock.toSqlInserts(data, {
entity: 'Users',
schema: 'sandbox',
table: 'UserSeed',
dialect: 'postgres',
columns: ['Id', 'Email', 'CreatedOn'],
batchSize: 100
});Reproducible Data
Seeded generation is optional. Without a seed, the package continues to use Math.random().
A seed may be a string or number. The simplest good seed is a stable, readable value that describes the test case. Use the same seed with the same template and options to replay the same generated data.
const template = {
Id: { $mock: 'guid', $version: 2 },
Name: { $mock: 'fullname' },
Status: { $mock: 'choose', choices: ['New', 'Open', 'Closed'] }
};
const first = mock.withSeed('case-001', () => mock.filledObject(template));
const second = mock.withSeed('case-001', () => mock.filledObject(template));
// first and second are equal
const third = mock.filledObject(template, { seed: 'case-001' });
// per-call seed injection is also supported from the options objectFor most tests, do not overthink the seed. Pick a short value that will stay the same when the test is renamed or moved.
const record = mock.filledObject(template, {
seed: 'claim-update-happy-path'
});For larger suites, build seed values from stable business context. This makes failures easier to replay and keeps unrelated tests from accidentally sharing the same generated data stream.
const seed = [
'claims-api',
'change-history',
'v2',
'update-with-attachments',
'case-001'
].join(':');
const records = mock.filledList(template, {
seed,
range: { min: 10, max: 10 }
});Good seed values are stable, descriptive, and safe to commit. Avoid Date.now(), random GUIDs, environment-specific paths, database IDs, secrets, or personal data. When the template changes in a meaningful way, include a schema or scenario version such as v2 so old snapshots and new snapshots do not pretend to be the same generated case.
mock.random.setSeed('suite-001');
const value = mock.filledList(template, {
range: { min: 2, max: 4 }
});
mock.random.clearSeed();Dates that use now or today can also be made deterministic with clock injection.
const fixedNow = '2024-07-15T16:45:30.005Z';
const dated = mock.withClock(fixedNow, () => mock.filledObject({
Id: { $mock: 'guid', $version: 2 },
CreatedOn: { $mock: 'date', min: 'today', max: 'now' }
}, { seed: 'case-001' }));
mock.clock.setClock(fixedNow);
const suiteValue = mock.filledObject({
CreatedOn: { $mock: 'date', min: 'today', max: 'now' }
});
mock.clock.clearClock();Legacy String DSL
The old v1 string shorthand exists only as a migration aid for existing templates. It is deprecated, disabled by default, and not recommended for new test suites or seed data. Prefer JSON DSL v2.
To run an old template temporarily, set allowLegacyDsl explicitly:
const sameStyle = mock.filledObject({
Id: 'guid',
CreatedOn: 'date:today:now'
}, { allowLegacyDsl: true });Without that switch, v1 strings throw DSLParseError with code: 'legacy_dsl_disabled'. The error message points to JSON DSL object examples first, then mentions { allowLegacyDsl: true } as the last-resort migration switch.
Legacy v1 strings are scalar value shorthand only. They cannot express JSON DSL metadata, $version, synthetic references, reference indexes, reference paths, dataset entities, entity options, template file paths, loader safety options, or future output adapters. When a dataset uses $ref, validation rejects any remaining v1 string DSL in that dataset with code: 'legacy_dsl_unsupported_feature'; convert those fields to JSON DSL v2 first.
In dataset files, string values in an entity's template field are treated as template file paths. A value like "template": "guid" is rejected with TemplateLoadError code legacy_template_source_unsupported. Use an inline JSON object template instead:
{
"entities": {
"Users": {
"template": {
"Id": { "$mock": "guid", "$version": 2 }
}
}
}
}Or point to a JSON template file explicitly:
{
"entities": {
"Users": {
"templatePath": "./templates/user.json"
}
}
}Potential Upcoming Features
1. JSON DSL composition for multi-part generated values.
2. Schema bridges for JSON Schema, Zod, and/or OpenAPI template generation.
3. A CLI with commands such as validate, generate, to-csv, to-sql, and pack.
4. Richer relationship modeling, including one-to-many, many-to-many, uniqueness, required foreign keys, weighted references, and relationship cardinality helpers.
5. Custom generator and plugin support for domain-specific test data.
6. Large-data streaming and batching for bigger non-production database fixture generation.
7. Dialect-specific database payload helpers beyond raw insert statements.
8. Official examples for Jest/Vitest snapshots, API mock data, SQL seed files, and non-production database seeding.
9. Release confidence improvements such as Bitbucket pipeline badges, coverage badges, npm provenance/2FA notes, and a changelog.Contributing
In lieu of a formal style guide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code.
