Skip to content

Seeders

Seeders populate your test database with predictable data before each test run. The Seeder base class from stratal/seeder runs inside the request-scoped container via TestingModule.seed(), giving each seeder access to dependency injection.

Extend the Seeder base class and implement the run() method. Use constructor injection to access your services:

import { Seeder } from 'stratal/seeder'
import { inject, Transient } from 'stratal/di'
import type { DatabaseService } from '@stratal/framework/database'
@Transient()
export class UserSeeder extends Seeder {
constructor(
@inject('Database') private readonly db: DatabaseService,
) {
super()
}
async run(): Promise<void> {
await this.db.user.create({
data: {
id: 'test-user-id',
email: 'user@test.com',
name: 'Test User',
emailVerified: true,
role: 'user',
},
})
}
}

Use TestingModule.seed() to run one or more seeders. Pass seeder class constructors (not instances):

import { Test, type TestingModule } from '@stratal/testing'
import { UserSeeder } from './seeders/user.seeder'
import { RoleSeeder } from './seeders/role.seeder'
const module = await Test.createTestingModule({
imports: [AppModule],
}).compile()
// Run a single seeder
await module.seed(UserSeeder)
// Run multiple seeders
await module.seed(UserSeeder, RoleSeeder)

Each seeder is resolved from the request-scoped container, so it has access to all registered providers via dependency injection.

Use this.call() inside a seeder to invoke another seeder:

import { Seeder } from 'stratal/seeder'
import { Transient } from 'stratal/di'
import { UserSeeder } from './user.seeder'
import { RoleSeeder } from './role.seeder'
@Transient()
export class DatabaseSeeder extends Seeder {
async run(): Promise<void> {
await this.call(UserSeeder)
await this.call(RoleSeeder)
}
}

A common pattern is to truncate the database and re-seed before each test:

import { describe, beforeEach, afterAll, it } from 'vitest'
import { Test, type TestingModule } from '@stratal/testing'
import { UserSeeder } from './seeders/user.seeder'
describe('UsersController', () => {
let module: TestingModule
beforeEach(async () => {
module = await Test.createTestingModule({
imports: [AppModule],
}).compile()
await module.truncateDb()
await module.seed(UserSeeder)
})
afterAll(async () => {
await module.close()
})
it('returns the seeded user', async () => {
await module.assertDatabaseHas('user', { id: 'test-user-id' })
})
})

A more complete seeder that creates multiple related records:

import { Seeder } from 'stratal/seeder'
import { inject, DI_TOKENS, Transient } from 'stratal/di'
import type { DatabaseService } from '@stratal/framework/database'
export const ADMIN_USER_ID = 'admin-user-id'
export const REGULAR_USER_ID = 'regular-user-id'
@Transient()
export class UserSeeder extends Seeder {
constructor(
@inject(DI_TOKENS.Database) private readonly db: DatabaseService,
) {
super()
}
async run(): Promise<void> {
const [admin, regular] = await Promise.all([
this.db.user.create({
data: {
id: ADMIN_USER_ID,
email: 'admin@test.com',
name: 'Admin User',
emailVerified: true,
role: 'admin',
},
}),
this.db.user.create({
data: {
id: REGULAR_USER_ID,
email: 'user@test.com',
name: 'Regular User',
emailVerified: true,
role: 'user',
},
}),
])
await this.db.account.createMany({
data: [
{
accountId: admin.id,
providerId: 'credential',
userId: admin.id,
password: 'hashed-password',
},
{
accountId: regular.id,
providerId: 'credential',
userId: regular.id,
password: 'hashed-password',
},
],
})
}
}