A delightful way to use factories in your code. Inspired by Factory Boy in Python, MikroORM seeding and the repositories from pleerock
Made with ❤️ by Jorge Bodega and contributors
Before using this TypeORM extension please read the TypeORM Getting Started documentation. This explains how to setup a TypeORM project.
After that, install the extension. Add development flag if you are not using factories in production code.
npm i [-D] @jorgebodega/typeorm-factory
yarn add [-D] @jorgebodega/typeorm-factory
pnpm add [-D] @jorgebodega/typeorm-factory
Isn't it exhausting to create some sample data for your database, well this time is over!
How does it work? Just create a entity factory.
@Entity()
export class Pet {
@PrimaryGeneratedColumn('increment')
id!: string
@Column()
name!: string
@ManyToOne(() => User, (user) => user.pets)
@JoinColumn({ name: 'owner_id' })
owner!: User
}
export class PetFactory extends Factory<Pet> {
protected entity = Pet
protected dataSource = dataSource
protected attrs(): FactorizedAttrs<Pet> {
return {
name: faker.animal.insect(),
owner: new LazyInstanceAttribute((instance) => new SingleSubfactory(UserFactory, { pets: [instance] })),
}
}
}
Factory is how we provide a way to simplify entities creation, implementing a factory creational pattern. It is defined as an abstract class with generic typing, so you have to extend over it.
class UserFactory extends Factory<User> {
protected entity = User
protected dataSource = dataSource // Imported datasource
protected attrs(): FactorizedAttrs<User> = {
...
}
}
make
& makeMany
Make and makeMany executes the factory functions and return a new instance of the given entity. The instance is filled with the generated values from the factory function, but not saved in the database.
make(overrideParams: Partial<FactorizedAttrs<T>> = {}): Promise<T>
makeMany(amount: number, overrideParams: Partial<FactorizedAttrs<T>> = {}): Promise<T[]>
new UserFactory().make()
new UserFactory().makeMany(10)
// override the email
new UserFactory().make({ email: 'other@mail.com' })
new UserFactory().makeMany(10, { email: 'other@mail.com' })
create
& createMany
the create and createMany method is similar to the make and makeMany method, but at the end the created entity instance gets persisted in the database using TypeORM entity manager.
create(overrideParams: Partial<FactorizedAttrs<T>> = {}, saveOptions?: SaveOptions): Promise<T>
createMany(amount: number, overrideParams: Partial<FactorizedAttrs<T>> = {}, saveOptions?: SaveOptions): Promise<T[]>
new UserFactory().create()
new UserFactory().createMany(10)
// override the email
new UserFactory().create({ email: 'other@mail.com' })
new UserFactory().createMany(10, { email: 'other@mail.com' })
// using save options
new UserFactory().create({ email: 'other@mail.com' }, { listeners: false })
new UserFactory().createMany(10, { email: 'other@mail.com' }, { listeners: false })
attrs
Attributes objects are superset from the original entity attributes.
protected attrs: FactorizedAttrs<User> = {
name: faker.person.firstName(),
lastName: async () => faker.person.lastName(),
email: new InstanceAttribute((instance) =>
[instance.name.toLowerCase(), instance.lastName.toLowerCase(), '@email.com'].join(''),
),
country: new Subfactory(CountryFactory),
}
Those factorized attributes resolves to the value of the original attribute, and could be one of the following types:
Nothing special, just a value with same type.
protected attrs(): FactorizedAttrs<User> = {
return {
name: faker.person.firstName(),
}
}
Function that could be sync or async, and return a value of the same type.
protected attrs: FactorizedAttrs<User> = {
return {
lastName: async () => faker.person.lastName(),
}
}
Class with a function that receive the current instance and returns a value of the same type. It is ideal for attributes that could depend on some others to be computed.
protected attrs: FactorizedAttrs<User> = {
return {
...,
email: new EagerInstanceAttribute((instance) =>
[instance.name.toLowerCase(), instance.lastName.toLowerCase(), '@email.com'].join(''),
),
}
}
In this simple case, if name
or lastName
override the value in any way, the email
attribute will be affected too.
There are two types of InstanceAttribute
:
EagerInstanceAttribute
: Executed after creation of the entity and before persisting it, so database id will be undefined.LazyInstanceAttribute
: Executed after creation of the entity and after persisting it.Just remember that, if you use make
or makeMany
, the only difference between EagerInstanceAttribute
and LazyInstanceAttribute
is that LazyInstanceAttribute
will be processed the last.
Subfactories are just a wrapper of another factory. This could help to avoid explicit operations that could lead to unexpected results over that factory, like
protected attrs: FactorizedAttrs<User> = {
country: async () => new CountryFactory().create({
name: faker.address.country(),
}),
}
instead of the same with
protected attrs: FactorizedAttrs<User> = {
country: new SingleSubfactory(CountryFactory, {
name: faker.address.country(),
}),
}
Subfactories could be created in two ways, allowing you to specify only the class or passing the instance already created. This could be useful if you have some specific class-related code in your factories:
protected attrs: FactorizedAttrs<User> = {
country: new SingleSubfactory(CountryFactory, {
name: faker.address.country(),
}),
// or
country: new SingleSubfactory(new CountryFactory(), {
name: faker.address.country(),
}),
}
Subfactory just execute the same kind of operation (make
or create
) over the factory. There are two types of Subfactory
:
SingleSubfactory
: Execute make
or create
to return a single element.CollectionSubfactory
: Execute makeMany
or createMany
to return an array of elements.A CollectionSubfactory
is equivalent now to an array of SingleSubfactory
, so this two statements produce the same result.
protected attrs: FactorizedAttrs<User> = {
pets: new CollectionSubfactory(PetFactory, 2, ...)
// or
pets: [
new SingleSubfactory(PetFactory, ...),
new SingleSubfactory(PetFactory, ...),
],
}
Some basic examples of how to use the library could be found on the examples
folder.