Skip to content

Commit

Permalink
feat: add subfactory as valid factorized attr
Browse files Browse the repository at this point in the history
  • Loading branch information
jorgebodega committed Feb 9, 2022
1 parent f0e0872 commit 0f4b37e
Show file tree
Hide file tree
Showing 11 changed files with 87 additions and 40 deletions.
5 changes: 2 additions & 3 deletions src/commands/seed.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@ import { configureConnection, getConnectionOptions } from '../connection'
import { Seeder } from '../seeder'
import { useSeeders } from '../useSeeders'
import { calculateFilePaths } from '../utils/fileHandling'
import type { ConnectionOptions } from '../types'
import type { ConnectionOptions, Constructable } from '../types'
import { SeederImportationError } from '../errors/SeederImportationError'
import { ClassConstructor } from '../types'

interface SeedCommandArguments extends Arguments {
root?: string
Expand Down Expand Up @@ -71,7 +70,7 @@ export class SeedCommand implements CommandModule {

// Show seeder in console
spinner.start('Importing Seeder')
let seeder!: ClassConstructor<Seeder>
let seeder!: Constructable<Seeder>
try {
const seederFiles = calculateFilePaths(options.seeders)
const seedersImported = await Promise.all(seederFiles.map((seederFile) => import(seederFile)))
Expand Down
30 changes: 20 additions & 10 deletions src/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@ export abstract class Factory<T> {
* Make a new entity without persisting it
*/
async make(overrideParams: Partial<FactorizedAttrs<T>> = {}): Promise<T> {
return this.makeEntity(overrideParams)
const attrs = { ...this.attrs, ...overrideParams }

const entity = await this.makeEntity(attrs, false)
await this.applyLazyAttributes(entity, attrs, false)

return entity
}

/**
Expand All @@ -30,10 +35,14 @@ export abstract class Factory<T> {
* Create a new entity and persist it
*/
async create(overrideParams: Partial<FactorizedAttrs<T>> = {}, saveOptions?: SaveOptions): Promise<T> {
const entity = await this.makeEntity(overrideParams)
const attrs = { ...this.attrs, ...overrideParams }
const entity = await this.makeEntity(attrs, true)

const connection = await fetchConnection()
return connection.createEntityManager().save<T>(entity, saveOptions)
const savedEntity = await connection.createEntityManager().save<T>(entity, saveOptions)
await this.applyLazyAttributes(savedEntity, attrs, true)

return savedEntity
}

/**
Expand All @@ -51,29 +60,30 @@ export abstract class Factory<T> {
return list
}

private async makeEntity(overrideParams: Partial<FactorizedAttrs<T>>) {
private async makeEntity(attrs: FactorizedAttrs<T>, shouldPersist: boolean) {
const entity = new this.entity()
const attrs = { ...this.attrs, ...overrideParams }

await Promise.all(
Object.entries(attrs).map(async ([key, value]) => {
Object.assign(entity, { [key]: await Factory.resolveValue(value) })
Object.assign(entity, { [key]: await Factory.resolveValue(value, shouldPersist) })
}),
)

return entity
}

private async applyLazyAttributes(entity: T, attrs: FactorizedAttrs<T>, shouldPersist: boolean) {
await Promise.all(
Object.entries(attrs).map(async ([key, value]) => {
if (value instanceof LazyAttribute) {
const newAttrib = value.resolve(entity)
Object.assign(entity, { [key]: await Factory.resolveValue(newAttrib) })
Object.assign(entity, { [key]: await Factory.resolveValue(newAttrib, shouldPersist) })
}
}),
)

return entity
}

private static resolveValue(value: unknown, shouldPersist = true) {
private static resolveValue(value: unknown, shouldPersist: boolean) {
if (value instanceof Subfactory) {
return shouldPersist ? value.create() : value.make()
} else if (value instanceof Function) {
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './connection'
export * from './factory'
export * from './lazyAttribute'
export * from './seeder'
export * from './types'
export * from './useSeeders'
4 changes: 2 additions & 2 deletions src/seeder.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import type { Connection } from 'typeorm'
import type { ClassConstructor } from './types'
import type { Constructable } from './types'

export abstract class Seeder {
abstract run(connection: Connection): Promise<void>

protected async call(connection: Connection, seeders: ClassConstructor<Seeder>[]): Promise<void> {
protected async call(connection: Connection, seeders: Constructable<Seeder>[]): Promise<void> {
for (const seeder of seeders) {
await new seeder().run(connection)
}
Expand Down
2 changes: 1 addition & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export type ConnectionConfiguration = {
connection: string
}
export type Constructable<T> = new () => T
export type FactorizedAttr<V> = V | (() => V | Promise<V>) | Subfactory<V>
export type FactorizedAttr<V> = V | (() => V | Promise<V>) | Subfactory<V extends Array<infer U> ? U : V>
export type FactorizedAttrs<T> = {
[K in keyof Partial<T>]: FactorizedAttr<T[K]> | LazyAttribute<T, T[K]>
}
Expand Down
8 changes: 4 additions & 4 deletions src/useSeeders.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { configureConnection, fetchConnection } from './connection'
import { Seeder } from './seeder'
import type { ClassConstructor, ConnectionConfiguration } from './types'
import type { ConnectionConfiguration, Constructable } from './types'

export async function useSeeders(entrySeeders: ClassConstructor<Seeder> | ClassConstructor<Seeder>[]): Promise<void>
export async function useSeeders(entrySeeders: Constructable<Seeder> | Constructable<Seeder>[]): Promise<void>
export async function useSeeders(
entrySeeders: ClassConstructor<Seeder> | ClassConstructor<Seeder>[],
entrySeeders: Constructable<Seeder> | Constructable<Seeder>[],
customOptions: Partial<ConnectionConfiguration>,
): Promise<void>

export async function useSeeders(
entrySeeders: ClassConstructor<Seeder> | ClassConstructor<Seeder>[],
entrySeeders: Constructable<Seeder> | Constructable<Seeder>[],
customOptions?: Partial<ConnectionConfiguration>,
): Promise<void> {
if (customOptions) configureConnection(customOptions)
Expand Down
10 changes: 8 additions & 2 deletions test/entities/Pet.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,13 @@ export class Pet {
@Column()
lastName!: string

@ManyToOne(() => User)
@JoinColumn({ name: 'user_id' })
@Column()
address!: string

@Column()
email!: string

@ManyToOne(() => User, (user) => user.pets)
@JoinColumn({ name: 'owner_id' })
owner!: User
}
13 changes: 11 additions & 2 deletions test/entities/User.entity.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm'
import { Entity, Column, PrimaryGeneratedColumn, OneToMany } from 'typeorm'
import { Pet } from './Pet.entity'

@Entity()
export class User {
Expand All @@ -12,5 +13,13 @@ export class User {
lastName!: string

@Column()
phone!: number
address!: string

@Column()
email!: string

@OneToMany(() => Pet, (pet) => pet.owner, {
nullable: true,
})
pets?: Pet[]
}
9 changes: 6 additions & 3 deletions test/factories/Pet.factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,21 @@ import faker from '@faker-js/faker'
import { Factory } from '../../src/factory'
import { LazyAttribute } from '../../src/lazyAttribute'
import { Subfactory } from '../../src/subfactory'
import type { FactorizedAttrs } from '../../src/types'
import { Pet } from '../entities/Pet.entity'
import { UserFactory } from './User.factory'

export class PetFactory extends Factory<Pet> {
protected entity = Pet
protected attrs = {
protected attrs: FactorizedAttrs<Pet> = {
name: faker.name.findName(),
lastName: async () => faker.name.findName(),
address: new LazyAttribute((instance) => async () => `${instance.name.toLowerCase()} address`),
email: new LazyAttribute((instance) => `${instance.name.toLowerCase()}@example.com`),
owner: new LazyAttribute(
(instance: Pet) =>
(instance) =>
new Subfactory(UserFactory, {
name: `${instance.name} owner`,
name: () => `${instance.name} owner`,
}),
),
}
Expand Down
9 changes: 6 additions & 3 deletions test/factories/User.factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@ import faker from '@faker-js/faker'
import { Factory } from '../../src/factory'
import { LazyAttribute } from '../../src/lazyAttribute'
import { Subfactory } from '../../src/subfactory'
import type { FactorizedAttrs } from '../../src/types'
import { User } from '../entities/User.entity'
import { PetFactory } from './Pet.factory'

export class UserFactory extends Factory<User> {
protected entity = User
protected attrs = {
protected attrs: FactorizedAttrs<User> = {
name: faker.name.findName(),
lastName: new LazyAttribute((instance: User) => faker.name.lastName()),
phone: new LazyAttribute((instance: User) => faker.random.number()),
lastName: async () => faker.name.findName(),
address: new LazyAttribute((instance) => async () => `${instance.name.toLowerCase()} address`),
email: new LazyAttribute((instance) => `${instance.name.toLowerCase()}@example.com`),
pets: new Subfactory(PetFactory, 2),
}
}
36 changes: 26 additions & 10 deletions test/factory.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Connection } from 'typeorm'
import { configureConnection, Factory, fetchConnection } from '../src'
import { Subfactory } from '../src/subfactory'
import { Pet } from './entities/Pet.entity'
import { User } from './entities/User.entity'
import { PetFactory } from './factories/Pet.factory'
Expand Down Expand Up @@ -29,23 +30,38 @@ describe(Factory, () => {
expect(userMaked).toBeInstanceOf(User)
expect(userMaked.id).toBeUndefined()
expect(userMaked.name).toBeDefined()
expect(userMaked.lastName).toBeDefined()
expect(userMaked.address).toBeDefined()
expect(userMaked.email).toBeDefined()
expect(userMaked.pets).toBeUndefined()
})

test('Should make a new entity related with multiple other factories', async () => {
const subfactory = new Subfactory(PetFactory, 2)
const userMaked = await userFactory.make({
pets: new Subfactory(PetFactory, 2),
})

expect(userMaked).toBeInstanceOf(User)
expect(userMaked.id).toBeUndefined()
expect(userMaked.name).toBeDefined()
expect(userMaked.lastName).toBeDefined()
expect(userMaked.address).toBeDefined()
expect(userMaked.email).toBeDefined()
expect(userMaked.pets).toBeUndefined()
})

test('Should make a new entity related with another factory', async () => {
const petMaked = await petFactory.make()

expect(petMaked).toBeInstanceOf(Pet)
expect(petMaked.owner).toBeInstanceOf(User)
expect(petMaked.owner.name).toBeDefined()
})

test('Should make a new entity with map function', async () => {
const mockFn = jest.fn()
const userMaked = await userFactory.map(mockFn).make()

expect(userMaked).toBeInstanceOf(User)
expect(userMaked.name).toBeDefined()
expect(mockFn).toHaveBeenCalled()
expect(petMaked.owner.id).toBeUndefined()
expect(petMaked.id).toBeUndefined()
expect(petMaked.name).toBeDefined()
expect(petMaked.lastName).toBeDefined()
expect(petMaked.address).toBeDefined()
expect(petMaked.email).toBeDefined()
})

test('Should make a new entity overriding params', async () => {
Expand Down

0 comments on commit 0f4b37e

Please sign in to comment.