typeorm-fixtures
typeorm-fixtures copied to clipboard
[Feature request] "Scenario": load fixtures from a single file and that can extend each other
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
andPost.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
}
I would welcome this feature, for the same reason @alexstrat describes.