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 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.
- overrideParams - Override some of the attributes of the entity.
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: '[email protected]' })
new UserFactory().makeMany(10, { email: '[email protected]' })
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.
- overrideParams - Override some of the attributes of the entity.
- saveOptions - Save options from TypeORM
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: '[email protected]' })
new UserFactory().createMany(10, { email: '[email protected]' })
// using save options
new UserFactory().create({ email: '[email protected]' }, { listeners: false })
new UserFactory().createMany(10, { email: '[email protected]' }, { listeners: false })
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
: Executemake
orcreate
to return a single element.CollectionSubfactory
: ExecutemakeMany
orcreateMany
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.