A lightweight and flexible TypeScript library for generating mock data for your tests with predefined structures,
functional and iterable field generators, traits, and post-processing capabilities.
It makes no assumptions about frameworks or libraries, and can be used with any test runner.
Mimicry-js was inspired by test-data-bot and offers more flexibility and advanced TypeScript support.
Table of Contents
Rather than creating random objects each time you want to test something in your system you can instead use a builder that can create fake data. This keeps your tests consistent and means that they always use data that replicates the real thing. If your tests work off objects close to the real thing they are more useful and there's a higher chance of them finding bugs.
With npm:
npm install --save-dev mimicry-jsor using Yarn:
yarn add --dev mimicry-jsUse the build function to create a builder. Just give a builder an object of fields you want to define:
import {build} from 'mimicry-js';
const builder = build({
fields: {
firstName: 'John',
lastName: 'Doe',
},
});
const profile = builder.one()
console.log(profile);
// { firstName: 'John', lastName: 'Doe' }Once you've created a builder, you can call the one method it returns to generate an instance of that object - in this case, a profile.
You can also use the many method to create an array of instances of your object. The method expects a number indicating how many objects to generate.
const profiles = builder.many(3)
console.log(profiles);
// [
// { firstName: 'John', lastName: 'Doe' },
// { firstName: 'John', lastName: 'Doe' },
// { firstName: 'John', lastName: 'Doe' }
// ]In the example above, you may notice that the objects returned by the builder are identical. This is not ideal for your tests, so mimicry-js allows you to use functions and iterators.
For example, a custom function that returns a single value from the set.
import {build} from 'mimicry-js';
// Returns one of the options
const getOneOf = <T>(options: T[]) => {
return options[Math.floor(Math.random() * options.length)];
};
const builder = build({
fields: {
firstName: () => getOneOf(['John', 'Andrew']),
lastName: 'Doe',
},
});
const profiles = builder.many(2);
console.log(profiles);
// [
// { firstName: 'John', lastName: 'Doe' },
// { firstName: 'Andrew', lastName: 'Doe' }
// ]The builder calls the specified function for the field when creating each instance.
Note
In this case, the builder correctly infers the type for the field.
const profiles: {
firstName: string
lastName: string
}[]So you can also use various external libraries to generate random values (e.g., Faker)
import {build} from 'mimicry-js';
import {faker} from '@faker-js/faker';
const builder = build({
fields: {
firstName: () => faker.person.firstName(),
lastName: () => faker.person.lastName(),
},
});Since by default, the builder calls the provided functions to get field values, mimicry-js offers the fixed decorator, which allows keeping a value unchanged.
For example, if we need the getName field in the generated object to be a function, we can wrap this function with fixed.
import {build, fixed} from 'mimicry-js';
const builder = build({
fields: {
type: 'plain',
getName: fixed(() => 'Plain object'),
},
});
const thing = builder.one();
console.log(thing.getName()); // --> "Plain object"Often you will be creating objects that have an ID that comes from a database, so you need to guarantee that it's unique. You can use sequence, which increments the value on each call, starting from 1 (in versions ≤ "1.2.1" from 0):
import {build, sequence} from 'mimicry-js';
const profileBuilder = build({
fields: {
id: sequence(),
firstName: 'John',
lastName: 'Doe',
},
});
const firstPerson = profileBuilder.one();
const secondPerson = profileBuilder.one();
const thirdPerson = profileBuilder.one();
// firstPerson.id === 1
// secondPerson.id === 2
// thirdPerson.id === 3If you need more control, you can pass sequence a function that will be called with the number. This is useful to ensure completely unique emails, for example:
import {build, sequence} from 'mimicry-js';
const profileBuilder = build({
fields: {
firstName: 'John',
lastName: 'Doe',
email: sequence(x => `john${x}@mail.com`),
},
});
const firstPerson = profileBuilder.one();
const secondPerson = profileBuilder.one();
const thirdPerson = profileBuilder.one();
// firstPerson.email === [email protected]
// secondPerson.email === [email protected]
// thirdPerson.email === [email protected]If you want an object to have a random value, picked from a list you control, you can use oneOf:
import {build, oneOf} from 'mimicry-js';
const userBuilder = build({
fields: {
name: oneOf(['John', 'Andrew', 'Mike']),
},
});
const user = userBuilder.one();
// user.name === "John" | "Andrew" | "Mike"If you need something to be either true or false, you can use bool:
import {build, bool} from 'mimicry-js';
const userBuilder = build({
fields: {
isActive: bool(),
},
});
const user = userBuilder.one();
// user.isActive === true | falseMimicry-js offers another one way to generate unique values. The unique function returns a single unique value from the provided set once on each call.
import {build, unique} from 'mimicry-js';
const userBuilder = build({
fields: {
firstName: unique(['John', 'Andrew', 'Mike']),
lastName: 'Doe',
},
});
const users = userBuilder.many(3);
console.log(users);
// [
// { firstName: 'Andrew', lastName: 'Doe' },
// { firstName: 'Mike', lastName: 'Doe' },
// { firstName: 'John', lastName: 'Doe' }
// ]
userBuilder.one(); // throws Error "No unique options left!"Warning
If there are no unused values left, unique throws an exception. Therefore, it's more appropriate to use this generator primarily in overrides to control the set of values.
Sometimes we need unique but related values. For example, simulating the creation date of an entity.
In such cases, you can use the withPrev decorator. It takes a function that has access to the result of the previous call of this function.
import {build, withPrev} from 'mimicry-js';
const userBuilder = build({
fields: {
name: 'John Doe',
createdAt: withPrev((prevTimestamp?: number) => {
const timestamp = prevTimestamp ?? new Date('2020').getTime();
return timestamp + 1000;
}),
},
});
const firstUser = userBuilder.one();
const secondUser = userBuilder.one();
const thirdUser = userBuilder.one();
// firstUser.createdAt === 1577836801000
// secondUser.createdAt === 1577836802000
// thirdUser.createdAt === 1577836803000Warning
Keep in mind that on the first call, the value will always be an undefined.
Also, you need to inform the builder about the type of the received argument if a generic type is not specified for the builder itself.
Note
The builder also supports passing nested plain objects with generators in fields.
Generates a random integer within the given range.
int(): number;
int(max: number): number;
int(min: number, max: number): number;min– (optional) Lower bound of the number. Default is 1.max– (optional) Upper bound of the number. Default is 1000.
If only one argument is provided, it's treated as max, and min is assumed to be 1.
int(); // Random integer between 1 and 1000
int(10); // Random integer between 1 and 10
int(5, 15); // Random integer between 5 and 15
int(15, 5); // Also works — range will be corrected to 5–15Respect seeding via seed method, ensuring deterministic output when needed.
Generates a random floating-point number within the given range.
float(): number;
float(max: number): number;
float(min: number, max: number): number;- min – (optional) Lower bound of the number. Default is 0.
- max – (optional) Upper bound of the number. Default is 1.
If only one argument is provided, it's treated as max, and min is assumed to be 0.
float(); // Random float between 0 and 1
float(10); // Random float between 0 and 10
float(5, 10); // Random float between 5 and 10
float(10, 5); // Also works — range will be corrected to 5–10Respect seeding via seed method, ensuring deterministic output when needed.
In some cases, you may need to reset the state of the sequence and unique generators. To do this, you can call the builder.reset() method:
import {build, sequence, unique} from 'mimicry-js';
const builder = build({
fields: {
id: sequence(),
name: unique('Sam', 'John', 'Mike'),
},
});
const firstSet = builder.many(3);
console.log(firstSet);
// [
// { id: 1, name: 'Sam' },
// { id: 2, name: 'John' },
// { id: 3, name: 'Mike' }
// ]
builder.reset();
const secondSet = builder.many(3);
console.log(secondSet);
// [
// { id: 1, name: 'Sam' },
// { id: 2, name: 'John' },
// { id: 3, name: 'Mike' }
// ]Note
Additionally, you can implement state resetting in custom iterators.
Often, we need not just plain objects but instances of classes. In this case, you can pass a postBuild function along with fields.
It allows you to transform the generated object as needed, for example, to create a class instance.
import {build} from 'mimicry-js';
class User {
constructor(
public id: number,
public name: string,
) {}
}
const userBuilder = build({
fields: {
id: 1,
firstName: 'John',
lastName: 'Doe',
},
postBuild: ({id, firstName, lastName}) => new User(id, `${firstName} ${lastName}`),
});
const user = userBuilder.one();
console.log(user);
// User { id: 1, name: 'John Doe' }Note
In this case, the builder infers the ones return type as User.
const user: User
We often need to generate a random object but control one of the values directly for the purpose of testing. When you call a builder you can pass in overrides which will override the builder defaults:
import {build, sequence, oneOf} from 'mimicry-js';
const userBuilder = build({
fields: {
id: sequence(),
name: oneOf('Sam', 'Andrew', 'Mike'),
},
});
const user = userBuilder.one({
overrides: {
id: 5,
name: 'John',
},
});
console.log(user);
// { id: 5, name: 'John' }If you need to edit the object directly, you can pass in a postBuild function when you call the builder. This will be called after Mimicry-js has generated the fake object, and lets you directly change it.
import {build, sequence, oneOf} from 'mimicry-js';
class User {
constructor(
public id: number,
public name: string,
) {}
}
const userBuilder = build({
fields: {
id: sequence(),
firstName: oneOf('Sam', 'Andrew', 'Mike'),
lastName: oneOf('Doe', 'Smith', 'Jackson'),
},
});
const user = userBuilder.one({
overrides: {
id: 5,
firstName: 'John',
lastName: 'Doe',
},
postBuild: ({id, firstName, lastName}) => new User(id, `${firstName} ${lastName}`),
});
console.log(user);
// User { id: 5, name: 'John Doe' }Note
In this case, the builder also determines that the return type of the one method has changed to User.
Using overrides and postBuild lets you easily customise a specific object that a builder has created.
Traits let you define a set of overrides for a builder that can easily be re-applied. Let's imagine you've got a users builder where users can have a support role and a certain email:
import {build, sequence, oneOf} from 'mimicry-js';
interface User {
id: number;
name: string;
role: 'customer' | 'support' | 'administrator';
email?: string;
}
const userBuilder = build<User>({
fields: {
id: sequence(),
name: oneOf('John', 'Andrew', 'Mike'),
role: oneOf('customer', 'support', 'administrator'),
},
traits: {
support: {
overrides: {
role: 'support',
email: '[email protected]',
},
},
},
});
const support = userBuilder.one({traits: 'support'});
console.log(support);
// { id: 1, name: 'John', role: 'support', email: 'support@mail.com' }Note that the support trait is specified above. As a result, the role and email fields will be overwritten on each call, and we don't have to do this manually using overrides:
const support = userBuilder.one({
overrides: {
role: 'support',
email: '[email protected]',
},
});So now building a support user is easy:
const support = userBuilder.one({traits: 'support'});Important
In the example above, a generic type is used to specify the User type for simplicity. However, it is highly recommended to explore better alternatives for type specification in the Best Practices for Using TypeScript Types section.
You can define and use multiple traits when building an object. Be aware that if two traits override the same value, the one passed in last wins:
import {build, sequence, oneOf} from 'mimicry-js';
interface User {
id: number;
name: string;
role: 'customer' | 'support' | 'administrator';
email?: string;
}
const userBuilder = build<User>({
fields: {
id: sequence(),
name: oneOf('John', 'Andrew', 'Mike'),
role: oneOf('customer', 'support', 'administrator'),
},
traits: {
customer: {
overrides: {
role: 'customer',
},
},
withContactDetails: {
overrides: {
email: '[email protected]',
},
},
},
});
const customer = userBuilder.one({traits: ['customer', 'withContactDetails']});
console.log(customer);
// { id: 1, name: 'John', role: 'customer', email: 'contact@mail.com' }Note
You can use overrides together with traits. In this case, values from overrides will override the corresponding ones from traits.
Sometimes we need to generate complex objects with related values. In this case, the builder allows passing fields as a function that returns an object to build and takes the result of the previous call.
For example, in the code below, the price field depends on the value of the count field. Moreover, the count field changes with each build, so we need access to the result of the previous call.
import {build, sequence} from 'mimicry-js';
interface Order {
count: number;
price: number;
}
const orderBuilder = build({
fields: (previous?: Order) => {
const count = previous ? previous.count + 1 : 1;
return {
count: sequence(),
price: 1000 * count,
};
},
});
const orders = orderBuilder.many(3);
console.log(orders);
// [
// { count: 1, price: 1000 },
// { count: 2, price: 2000 },
// { count: 3, price: 3000 }
// ]Warning
Note that the value of the previous build will always be undefined on the first call.
Also, you need to inform the builder about the type of the received argument if a generic type is not specified for the builder itself.
Note
The builder preserves iterators after the first function call and continues using them instead of creating new ones, even though the function passed as fields is called each time.
If you need more control when creating objects, you can use generator functions. This allows you to define and manage the generation logic at each iteration.
For this, Mimicry-js provides the generate decorator, which expects a generator function as an argument:
import {build, generate} from 'mimicry-js';
function* timePeriodsGenerator() {
let currentStart = new Date('2025-01-01').getTime();
const periodDurationHs = 24;
const periodDurationMs = periodDurationHs * 60 * 60 * 1000; // Hours value in milliseconds
while (true) {
const currentEnd = currentStart + periodDurationMs;
yield {start: new Date(currentStart), end: new Date(currentEnd)};
currentStart = currentEnd;
}
}
const builder = build({
fields: generate(timePeriodsGenerator),
});
const periods = builder.many(3);
console.log(periods);
// [
// { start: 2025-01-01T00:00:00.000Z, end: 2025-01-02T00:00:00.000Z },
// { start: 2025-01-02T00:00:00.000Z, end: 2025-01-03T00:00:00.000Z },
// { start: 2025-01-03T00:00:00.000Z, end: 2025-01-04T00:00:00.000Z }
// ]Important
It is important to note that only infinite generators are supported.
The provided generator function is called each time the many or one methods are called.
This means that each build will be independent of the others. This is necessary to prevent unrelated tests from affecting each other:
const builder = build({
fields: generate(timePeriodsGenerator),
});
const firstPeriodsSet = builder.many(3);
const secondPeriodsSet = builder.many(3);
console.log(firstPeriodsSet);
// [
// { start: 2025-01-01T00:00:00.000Z, end: 2025-01-02T00:00:00.000Z },
// { start: 2025-01-02T00:00:00.000Z, end: 2025-01-03T00:00:00.000Z },
// { start: 2025-01-03T00:00:00.000Z, end: 2025-01-04T00:00:00.000Z }
// ]
console.log(secondPeriodsSet);
// [
// { start: 2025-01-01T00:00:00.000Z, end: 2025-01-02T00:00:00.000Z },
// { start: 2025-01-02T00:00:00.000Z, end: 2025-01-03T00:00:00.000Z },
// { start: 2025-01-03T00:00:00.000Z, end: 2025-01-04T00:00:00.000Z }
// ]Note
This does not apply to nested field generators, which are preserved after the first generation.
It can be very useful to pass some initial values to the generator function at the moment of object generation.
So, BuildTimeConfig has an optional initialParameters field, which accepts a tuple of arguments taken by the generator function:
import {build, generate} from 'mimicry-js';
function* timePeriodsGenerator(currentStartDate: Date, periodDurationInMs: number) {
let currentStart = currentStartDate.getTime();
while (true) {
const currentEnd = currentStart + periodDurationInMs;
yield {start: new Date(currentStart), end: new Date(currentEnd)};
currentStart = currentEnd;
}
}
const builder = build({
fields: generate(timePeriodsGenerator),
});
const start = new Date('2025-01-01');
const duration = 24 * 60 * 60 * 1000;
const periods = builder.many(3, {
initialParameters: [start, duration],
});
console.log(periods);
// [
// { start: 2025-01-01T00:00:00.000Z, end: 2025-01-02T00:00:00.000Z },
// { start: 2025-01-02T00:00:00.000Z, end: 2025-01-03T00:00:00.000Z },
// { start: 2025-01-03T00:00:00.000Z, end: 2025-01-04T00:00:00.000Z }
// ]Tip
With generate, the builder can validate the types of initialParameters.
// TS2322: Type [] is not assignable to type
// [currentStartDate: Date, periodDurationInMs: number]
// Source has 0 element(s) but target requires 2
const periods = builder.many(3, {
initialParameters: [],
});You can still use generators and functions to create field values:
import {build, generate, oneOf} from 'mimicry-js';
function* timePeriodsGenerator(currentStartDate: Date, periodDurationInMs: number) {
let currentStart = currentStartDate.getTime();
while (true) {
const currentEnd = currentStart + periodDurationInMs;
yield {
id: sequence(),
start: new Date(currentStart),
end: new Date(currentEnd),
type: oneOf('open', 'closed')
};
currentStart = currentEnd;
}
}
const builder = build({
fields: generate(timePeriodsGenerator),
});
const start = new Date('2025-01-01');
const duration = 24 * 60 * 60 * 1000;
const periods = builder.many(3, {
initialParameters: [start, duration],
});
console.log(periods);
// [
// {
// id: 1,
// start: 2025-01-01T00:00:00.000Z,
// end: 2025-01-02T00:00:00.000Z,
// type: 'open'
// },
// {
// id: 2,
// start: 2025-01-02T00:00:00.000Z,
// end: 2025-01-03T00:00:00.000Z,
// type: 'closed'
// },
// {
// id: 3,
// start: 2025-01-03T00:00:00.000Z,
// end: 2025-01-04T00:00:00.000Z,
// type: 'open'
// }
// ]Note
The builder preserves iterators after the first generator function iteration and continues using them instead of creating new ones.
You can also get the result of the previous build:
import {build, generate} from 'mimicry-js';
type Period = {
start: Date;
end: Date;
};
function* timePeriodsGenerator(currentStartDate: Date, periodDurationInMs: number) {
let currentStart = currentStartDate.getTime();
while (true) {
const previousBuildResult: Period = yield {
start: new Date(currentStart),
end: new Date(currentStart + periodDurationInMs),
};
currentStart = previousBuildResult.end.getTime();
}
}
const builder = build({
fields: generate(timePeriodsGenerator),
});
const start = new Date('2025-01-01');
const duration = 24 * 60 * 60 * 1000;
const periods = builder.many(3, {
initialParameters: [start, duration],
});
console.log(periods);
// [
// { start: 2025-01-01T00:00:00.000Z, end: 2025-01-02T00:00:00.000Z },
// { start: 2025-01-02T00:00:00.000Z, end: 2025-01-03T00:00:00.000Z },
// { start: 2025-01-03T00:00:00.000Z, end: 2025-01-04T00:00:00.000Z }
// ]Important
You need to specify the type of the value received via yield manually.
Let's imagine that one of the object's fields is another object that also requires fake data. The builder supports using field generators in nested objects:
import {build, sequence, oneOf} from 'mimicry-js';
interface Account {
id: number;
name: string;
address: {
apartment: string;
street: string;
city: string;
postalCode: number;
};
}
const builder = build<Account>({
fields: {
id: sequence(),
name: 'John',
address: {
apartment: sequence((x) => x.toString()),
street: oneOf('123 Main St', '456 Elm Ave'),
city: oneOf('New York', 'Los Angeles'),
postalCode: sequence((x) => x + 1000),
},
},
});
const account = builder.one();
console.log(account);
// {
// id: 1,
// name: 'John',
// address: {
// apartment: '1',
// street: '456 Elm Ave',
// city: 'Los Angeles',
// postalCode: 1000
// }
// }Note
You can just as easily create a separate builder for the address object and use it, but in this case, the data will be static.
Warning
Note that in this case, you must specify the type to ensure the builder correctly infers types. However, it is highly recommended to explore better alternatives for type specification in the Best Practices for Using TypeScript Types section.
When using this builder, we may need to override certain fields, such as city and street of the address.
So, we can do that:
const account = builder.one({
overrides: {
address: {
city: 'San Francisco',
street: '101 Pine Ln',
},
},
});
console.log(account);
// {
// id: 1,
// name: 'John',
// address: {
// apartment: '1',
// street: '101 Pine Ln',
// city: 'San Francisco',
// postalCode: 1000
// }
// }You may notice that we don't need to specify all the fields of the address object in overrides.
This behavior is also similar for traits:
const builder = build<Account>({
fields: {
id: sequence(),
name: 'John',
address: {
apartment: sequence((x) => x.toString()),
postalCode: sequence((x) => x + 1000),
street: '',
city: '',
},
},
traits: {
NY: {
overrides: {
address: {
street: '123 Main St',
city: 'New York',
},
},
},
LA: {
overrides: {
address: {
street: '456 Elm Ave',
city: 'Los Angeles',
},
},
},
},
});
const account = builder.one({
traits: 'LA',
});
console.log(account);
// {
// id: 1,
// name: 'John',
// address: {
// apartment: '1',
// postalCode: 1000,
// street: '456 Elm Ave',
// city: 'Los Angeles'
// }
// }The builder checks the values of arrays in the provided fields to handle nested generators.
import {build, sequence, oneOf} from 'mimicry-js';
interface Account {
id: number;
name: string;
addresses: Array<{
apartment: string;
street: string;
city: string;
postalCode: number;
}>;
}
const builder = build<Account>({
fields: {
id: sequence(),
name: 'John',
addresses: [],
},
});
const account = builder.one({
overrides: {
addresses: [
{
apartment: sequence((x) => x.toString()),
street: oneOf('456 Elm Ave'),
city: oneOf('Los Angeles'),
postalCode: 98101,
},
{
apartment: sequence((x) => x.toString()),
street: oneOf('101 Pine Ln'),
city: oneOf('San Francisco'),
postalCode: 10001,
},
],
},
});
console.log(account);
// {
// id: 1,
// name: 'John',
// addresses: [
// {
// apartment: '1',
// street: '456 Elm Ave',
// city: 'Los Angeles',
// postalCode: 98101
// },
// {
// apartment: '1',
// street: '101 Pine Ln',
// city: 'San Francisco',
// postalCode: 10001
// }
// ]
// }Note
However, the builder does not perform deep merging of arrays in traits and overrides.
Warning
Note that in this case, you must specify the type to ensure the builder correctly infers types. However, it is highly recommended to explore better alternatives for type specification in the Best Practices for Using TypeScript Types section.
You can also use custom iterators to generate field values:
import {build} from 'mimicry-js';
function* exponentiation(initialValue: number) {
let exponent = 0;
while (true) {
yield initialValue ** ++exponent;
}
}
const builder = build({
fields: {
exponent: exponentiation(2),
},
});
const [first, second, third] = builder.many(3);
// first.exponent === 2
// second.exponent === 4
// third.exponent === 8Important
Keep in mind that only infinite generators are supported.
The builder can reset the state of sequence and unique by calling the builder.reset() method. You can track this method call to reset values in your custom generator function.
To facilitate this, Mimicry-js provides the resetable utility, which allows managing state within a generator.
It takes an initial value and returns a Resetable instance with three methods: val, set, and use.
valprovides access to the current state.setallows updating the state; it takes a new value and returns the updated one.usesubscribes a specificResetableinstance toResetSignal.
In the example below, when builder.reset() is called, the val state resets to its initial value, which in this case is zero:
import {build, resetable} from 'mimicry-js';
function* exponentiation(initialValue: number) {
const {val, set, use} = resetable(0);
while (true) {
use(yield initialValue ** set(val() + 1));
}
}
const builder = build({
fields: {
exponent: exponentiation(2),
},
});
const firstSet = builder.many(3);
console.log(firstSet); // [ { exponent: 2 }, { exponent: 4 }, { exponent: 8 } ]
builder.reset();
const secondSet = builder.many(3);
console.log(secondSet); // [ { exponent: 2 }, { exponent: 4 }, { exponent: 8 } ]This example may seem somewhat complex to understand. However, there is no "magic" happening here.
If you take a closer look at the exponentiation type inferred by TypeScript in the example above, you'll see that the Generator accepts ResetSignal as the TNext generic type.
function exponentiation(initialValue: number): Generator<number, void, ResetSignal>Tip
When the builder calls the next() method on your iterator, it passes an instance of ResetSignal as an argument, which is then returned by the yield operator inside the generator function.
This example could be rewritten in a more explicit form, but in that case, you would need to define ResetSignal type definition yourself.
import {build, resetable, ResetSignal} from 'mimicry-js';
function* exponentiation(initialValue: number) {
const {val, set, use} = resetable(0);
while (true) {
const exponent = val() + 1;
set(exponent);
const signal: ResetSignal = yield initialValue ** exponent;
use(signal);
}
}
const builder = build({
fields: {
exponent: exponentiation(2),
},
});
const firstSet = builder.many(3);
console.log(firstSet); // [ { exponent: 2 }, { exponent: 4 }, { exponent: 8 } ]
builder.reset();
const secondSet = builder.many(3);
console.log(secondSet); // [ { exponent: 2 }, { exponent: 4 }, { exponent: 8 } ]Important
To avoid errors, try to implement your generator function in such a way that all state updates are performed before returning a value using the yield operator in an infinite loop.
If you update the state after returning from yield, the code will resume execution right after yield in the next iteration.
This means that immediately after resetting the state, you will update it again, causing the val value in the next while loop iteration to differ from its initial value (the one passed to resetable).
import {build, resetable} from 'mimicry-js';
function* exponentiation(initialValue: number) {
const {val, set, use} = resetable(1);
while (true) {
const exponent = val() + 1;
use(yield initialValue ** exponent);
set(exponent);
}
}
const builder = build({
fields: {
exponent: exponentiation(2),
},
});
const firstSet = builder.many(3);
console.log(firstSet); // [ { exponent: 2 }, { exponent: 4 }, { exponent: 8 } ]
builder.reset();
const secondSet = builder.many(3);
console.log(secondSet); // [ { exponent: 32 }, { exponent: 64 }, { exponent: 128 } ]Caution
The state was not reset because it was updated with exponent from the previous iteration!
Mimicry-js provides the ability to ensure consistent values generated by the built-in value generators.
Used to set a seed for the random value generator used by oneOf, unique, int, and float.
Sets the seed or generates a new one.
import {build, seed} from 'mimicry-js';
const builder = build({
fields: {
value: oneOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10),
},
});
seed(33);
const firstSet = builder.many(4);
console.log(firstSet); // [{ value: 6 }, { value: 6 }, { value: 8 }, { value: 8 }]
seed(42);
const secondSet = builder.many(4);
console.log(secondSet); // [{ value: 2 }, { value: 8 }, { value: 8 }, { value: 10 }]
seed(42);
const thirdSet = builder.many(4);
console.log(thirdSet); // [{ value: 2 }, { value: 8 }, { value: 8 }, { value: 10 }]Note
Please note that generated values are dependent on both the seed and the number of calls that have been made since it was set.
In addition to that it can be used for creating truly random tests (by passing no arguments), that still can be reproduced if needed, by logging the result and explicitly setting it if needed.
You also have access to the getSeed method, which returns the current seed value:
import {getSeed} from 'mimicry-js';
const currentSeed = getSeed();
console.log(currentSeed); // 955Mimicry-js is written in TypeScript and ships with the types generated so if you're using TypeScript you will get type support out the box.
The builder below, in addition to the object with fields, has a set of traits and a postBuild transformer.
import {build} from 'mimicry-js';
class Profile {
name: string;
age?: number;
constructor({firstName, lastName, age}: IProfileData) {
this.name = `${firstName} ${lastName}`;
this.age = age;
}
}
const profile = {
firstName: 'John',
lastName: 'Doe',
age: 30,
};
const builder = build({
fields: profile,
traits: {
younger: {
overrides: {
age: 18,
},
},
older: {
overrides: {
age: 50,
},
},
},
postBuild: (generatedFields) => new Profile(generatedFields),
});And it has all the information about the input data types, trait names, and the result type:
const builder: Builder<{
firstName: string
lastName: string
age: number
}, Profile, "younger" | "older", never>Tip
The never type at the end indicates the type of initialParameters when using GeneratorFunction to create fields.
So, in most situations, types don’t need to be specified manually, except for cases with generators in nested objects and arrays.
Important
If you manually specify the builder object's generic, the builder loses information about the specific traits names (the type becomes string) and initialParameters (the type becomes never). This is due to TypeScript's behavior: default values are used for all generics if even one of them is provided.
const builder = build<Profile>({ ... });const builder: Builder<Profile, Profile, string, never>So, if you want to get type checking and use type-based code suggestions when filling out fields, while allowing the builder to infer types automatically, you can use the built-in FieldsConfiguration type:
import {build, sequence, FieldsConfiguration} from 'mimicry-js';
interface IProfileData {
id: number;
firstName: string;
lastName: string;
age?: number;
}
const profile: FieldsConfiguration<IProfileData> = {
id: sequence(),
firstName: 'John',
lastName: 'Doe',
age: 30,
};
const builder = build({
fields: profile,
postBuild: (generatedFields) => new Profile(generatedFields),
traits: {
younger: {
overrides: {
age: 18,
},
},
older: {
overrides: {
age: 50,
},
},
},
});As a result, the builder retains all the type information and can validate the trait names passed to it, while you get all the TypeScript checks when filling out the fields.
const builder: Builder<IProfileData, Profile, "younger" | "older", never>builder.one({
traits: 'other'
})
// TS2322: Type "other" is not assignable to type
// "younger" | "older" | ("younger" | "older")[] | undefinedSimilarly, in the case of using GeneratorFunction:
import {build, withPrev, FieldsConfiguration} from 'mimicry-js';
interface IProfileData {
id: number;
firstName: string;
lastName: string;
age?: number;
}
const builder = build({
fields: generate(function* (startId: number) {
while (true) {
const profile: FieldsConfiguration<IProfileData> = {
id: withPrev((previous) => (previous ? previous + 1 : startId)),
firstName: 'John',
lastName: 'Doe',
age: 30,
};
yield profile;
}
}),
postBuild: (generatedFields) => new Profile(generatedFields),
traits: {
younger: {
overrides: {
age: 18,
},
},
older: {
overrides: {
age: 50,
},
},
},
});As a result:
const builder: Builder<IProfileData, Profile, "younger" | "older", [startId: number]>MIT