Create objects from blueprints using reusable builders.
Too abstract? Here's an example where we use @ngxp/builder to create test data for a log.
import { Blueprint, createBlueprintBuilder } from './src';
// we're using faker to create random values
import * as faker from 'faker';
interface LogEntry {
date: Date;
author: string;
message: string;
}
// a blueprint provides a value generator function
// for each property of our object
const logEntryBlueprint: Blueprint<LogEntry> = {
date: () => faker.date.past(),
author: () => faker.name.findName(),
message: () => faker.lorem.sentences()
};
// each time the builder's build method is called, the
// methods of the blueprint are used to retrieve
// a new set of values for the object to be created
const logEntryBuilder = createBlueprintBuilder(logEntryBlueprint);
// create some log entries
const log = [
logEntryBuilder().build(),
logEntryBuilder().build()
];
log
now contains this array:
[
{
"date": "2018-10-22T12:31:52.169Z",
"author": "Jevon Hintz V",
"message": "Sit repellat consequatur fugit qui. Tempore vero aut."
},
{
"date": "2019-02-06T09:50:54.421Z",
"author": "Libby Crist I",
"message": "Aspernatur tempore quia molestiae praesentium ut sed quia aperiam consequatur. Culpa hic enim blanditiis recusandae iste maiores."
}
]
When creating test data, it can be useful to create a lot of objects at once. Just use buildMany
instead of build
:
const log = logEntryBuilder().buildMany(100);
You can override some of the values using either the optional values
parameter of createBlueprintBuilder
or setter methods that are named according to the blueprint:
const logEntry = logEntryBuilder({
author: `Miles O'Brien`
})
.message(`I'm bored...`)
.buildMany(2);
These values are always used instead of the ones generated by the blueprint:
[
{
"date": "2018-06-02T04:49:24.531Z",
"author": "Miles O'Brien",
"message": "I'm bored..."
},
{
"date": "2018-09-10T09:33:57.386Z",
"author": "Miles O'Brien",
"message": "I'm bored..."
}
]
When creating test data, it can be useful to make the data immutable to prevent tests from changing data and thereby affecting other tests. Luckily, we have a freeze
method!
const testLogEntry = logEntryBuilder()
.freeze()
.build();
testLogEntry.date = new Date(); // throws ☠
You can pass transformation functions to transform the object being built. Transformations are always applied after any value setters.
const scream: Transformation<LogEntry> = entry => ({
...entry,
message: entry.message!.toUpperCase()
});
const logEntry = logEntryBuilder()
.transform(scream)
.message(`I'm bored...`)
.build();