typeorm-fixtures icon indicating copy to clipboard operation
typeorm-fixtures copied to clipboard

[Feature request] "Scenario": load fixtures from a single file and that can extend each other

Open alexstrat opened this issue 3 years ago β€’ 1 comments

I'm (programmatically) using typeorm-fixtures for integration testing: on beforeEach I load a fixtures folder to set my DB in a certain state (let's call it a "scenario") and run my tests against it.

It works well, but, along the path, I found the workflow a bit irritating because:

  • difficult to have a comprehensive view of the desired DB scenario because the fixtures have to be disseminated in several files. Example: to have a scenario with a user completed profile and their post, the scenario information is in 3 files User.yaml, Profile.yaml and Post.yaml
  • some scenarios have a lot in commons but I need to duplicate the whole fixtures folder for each scenario. Example: in a scenario A I have a user completed profile and their post; and in scenario B, I have a user completed profile and their comment on a post

Still along the path, I'm finally writing my scenarios in a scenario file that looks like this. I load them, resolve dependencies and then load the result into typeorm-fixtures's Resolver and Builder:

description:  A user completed profile and their comment on a Post
extends:
  - './base-user-with-profile.scenario.yaml'
fixtures:
  - entity: User
    items:
      writer:
        name: Walt
  - entity: Post
    items:
      post:
        user: '@writer'
        content: Awesome
  - entity: Comment
    items:
      user_post:
        user: '@user'
        content: Super post

It would be cool if this could inspire upcoming features!

If anyone interested here is what i'm using:

import * as path from 'path'
import * as yaml from 'js-yaml'
import * as fs from 'fs'
import { Builder, fixturesIterator, Parser, Resolver, IFixturesConfig } from 'typeorm-fixtures-cli'

type IScenerioConfig = {
  description?: string
  extends?: string[]
  fixtures?: IFixturesConfig[]
}

const loadFixturesFromScenario = (scenarioPath: string): IFixturesConfig[] => {
  const scenariosFixturesConfigs: IFixturesConfig[] = []
  const visitedScenarioPaths: string[] = []

  const visitScenario = (currentScenarioPath: string) => {
    const absolutePath = path.normalize(currentScenarioPath)
    if (visitedScenarioPaths.includes(absolutePath)) return
    visitedScenarioPaths.push(absolutePath)

    const currentScenario = yaml.load(fs.readFileSync(absolutePath).toString()) as IScenerioConfig

    if (currentScenario.extends) {
      // eslint-disable-next-line no-restricted-syntax
      for (const relativePath of currentScenario.extends) {
        const depScenarioPath = path.resolve(path.dirname(currentScenarioPath), relativePath)
        visitScenario(depScenarioPath)
      }
    }

    if (currentScenario.fixtures) {
      currentScenario.fixtures.forEach((f) => scenariosFixturesConfigs.push(f))
    }
  }

  visitScenario(scenarioPath)

  return scenariosFixturesConfigs
}

export type FixtureEntities = Record<string, Record<string, any>>

export const loadScenario = async (scenarioPath: string): Promise<FixtureEntities> => {
  const connection = getConnection(process.env.NODE_ENV)

  const fixtureConfigs = loadFixturesFromScenario(scenarioPath)

  const resolver = new Resolver()
  const fixtures = resolver.resolve(fixtureConfigs)
  const builder = new Builder(connection, new Parser())

  const res: FixtureEntities = {}
  // eslint-disable-next-line no-restricted-syntax
  for (const fixture of Array.from(fixturesIterator(fixtures))) {
    // eslint-disable-next-line no-await-in-loop
    const entity = await builder.build(fixture)
    // eslint-disable-next-line no-await-in-loop
    const savedEntity = await connection.getRepository(entity.constructor.name).save(entity)

    res[fixture.entity] = {
      ...(res[fixture.entity] || {}),
      [fixture.name]: savedEntity,
    }
  }
  return res
}

alexstrat avatar Jan 12 '22 16:01 alexstrat

I would welcome this feature, for the same reason @alexstrat describes.

ikilinc avatar Aug 31 '22 14:08 ikilinc