Skip to content

Fixtures


Test fixtures provide consistent, reusable test data across your test suite.

Fixture Architecture
================================================================================
+------------------+
| Test File |
+------------------+
|
v
+------------------+
| Fixture | <-- Reusable data definitions
| Factory |
+------------------+
|
v
+------------------+
| Entity | <-- TypeORM entities
+------------------+
Benefits:
- Consistent test data
- Easy to maintain
- Reduces test setup code
- Supports relationships
================================================================================

test/fixtures/user.fixture.ts
import { User } from '../../src/users/user.entity';
export class UserFixture {
private static counter = 0;
static create(overrides: Partial<User> = {}): User {
this.counter++;
const user = new User();
user.name = overrides.name || `Test User ${this.counter}`;
user.email = overrides.email || `test${this.counter}@example.com`;
user.password = overrides.password || 'hashed_password';
user.isActive = overrides.isActive ?? true;
user.role = overrides.role || 'user';
return user;
}
static createMany(count: number, overrides: Partial<User> = {}): User[] {
return Array.from({ length: count }, () => this.create(overrides));
}
static reset() {
this.counter = 0;
}
}
// Usage
const user = UserFixture.create({ name: 'John Doe' });
const users = UserFixture.createMany(5, { isActive: true });
test/fixtures/user.builder.ts
import { User } from '../../src/users/user.entity';
export class UserBuilder {
private user: Partial<User> = {
name: 'Default User',
email: 'default@example.com',
password: 'hashed_password',
isActive: true,
role: 'user',
};
withName(name: string): UserBuilder {
this.user.name = name;
return this;
}
withEmail(email: string): UserBuilder {
this.user.email = email;
return this;
}
withPassword(password: string): UserBuilder {
this.user.password = password;
return this;
}
asAdmin(): UserBuilder {
this.user.role = 'admin';
return this;
}
asInactive(): UserBuilder {
this.user.isActive = false;
return this;
}
build(): User {
const user = new User();
Object.assign(user, this.user);
return user;
}
}
// Usage
const admin = new UserBuilder()
.withName('Admin User')
.withEmail('admin@example.com')
.asAdmin()
.build();
const inactiveUser = new UserBuilder()
.withName('Inactive User')
.asInactive()
.build();

test/fixtures/post.fixture.ts
import { Post } from '../../src/posts/post.entity';
import { UserFixture } from './user.fixture';
export class PostFixture {
private static counter = 0;
static create(overrides: Partial<Post> = {}): Post {
this.counter++;
const post = new Post();
post.title = overrides.title || `Test Post ${this.counter}`;
post.content = overrides.content || 'Test content';
post.isPublished = overrides.isPublished ?? true;
post.viewCount = overrides.viewCount || 0;
return post;
}
static createForUser(userId: number, overrides: Partial<Post> = {}): Post {
const post = this.create(overrides);
post.authorId = userId;
return post;
}
static createWithAuthor(
userOverrides: Partial<User> = {},
postOverrides: Partial<Post> = {},
): { post: Post; author: User } {
const author = UserFixture.create(userOverrides);
const post = this.create(postOverrides);
post.author = author;
return { post, author };
}
static createManyForUser(
userId: number,
count: number,
overrides: Partial<Post> = {},
): Post[] {
return Array.from({ length: count }, () =>
this.createForUser(userId, overrides),
);
}
}
// Usage
const post = PostFixture.createForUser(1, { title: 'My First Post' });
const { post, author } = PostFixture.createWithAuthor({ name: 'John' });
test/fixtures/comment.fixture.ts
import { Comment } from '../../src/comments/comment.entity';
import { UserFixture } from './user.fixture';
import { PostFixture } from './post.fixture';
export class CommentFixture {
private static counter = 0;
static create(overrides: Partial<Comment> = {}): Comment {
this.counter++;
const comment = new Comment();
comment.content = overrides.content || `Test comment ${this.counter}`;
comment.isApproved = overrides.isApproved ?? true;
return comment;
}
static createFullScenario(): {
author: User;
commenter: User;
post: Post;
comment: Comment;
} {
const author = UserFixture.create({ name: 'Post Author' });
const commenter = UserFixture.create({ name: 'Commenter' });
const post = PostFixture.create({ title: 'Test Post' });
post.author = author;
const comment = this.create();
comment.post = post;
comment.author = commenter;
return { author, commenter, post, comment };
}
}

test/fixtures/fixture-loader.service.ts
import { DataSource, Repository } from 'typeorm';
export class FixtureLoader {
constructor(private dataSource: DataSource) {}
async load<T>(entity: new () => T, data: Partial<T>[]): Promise<T[]> {
const repository = this.dataSource.getRepository(entity) as Repository<T>;
const entities = repository.create(data);
return repository.save(entities);
}
async loadOne<T>(entity: new () => T, data: Partial<T>): Promise<T> {
const [result] = await this.load(entity, [data]);
return result;
}
async loadUsers(count: number = 5): Promise<User[]> {
const users = UserFixture.createMany(count);
return this.load(User, users);
}
async loadPostsWithAuthors(postCount: number = 5): Promise<{ users: User[]; posts: Post[] }> {
const users = await this.loadUsers(2);
const posts = PostFixture.createMany(postCount).map((post, i) => ({
...post,
authorId: users[i % users.length].id,
}));
const savedPosts = await this.load(Post, posts);
return { users, posts: savedPosts };
}
async loadFullBlogScenario(): Promise<{
users: User[];
posts: Post[];
comments: Comment[];
}> {
// Create users
const users = await this.loadUsers(3);
// Create posts
const posts = await this.load(Post, [
PostFixture.createForUser(users[0].id, { title: 'Post 1' }),
PostFixture.createForUser(users[0].id, { title: 'Post 2' }),
PostFixture.createForUser(users[1].id, { title: 'Post 3' }),
]);
// Create comments
const comments = await this.load(Comment, [
{ ...CommentFixture.create(), postId: posts[0].id, authorId: users[1].id },
{ ...CommentFixture.create(), postId: posts[0].id, authorId: users[2].id },
{ ...CommentFixture.create(), postId: posts[1].id, authorId: users[1].id },
]);
return { users, posts, comments };
}
}
// Usage in tests
describe('With FixtureLoader', () => {
let loader: FixtureLoader;
beforeEach(async () => {
loader = new FixtureLoader(dataSource);
});
it('should load test data', async () => {
const users = await loader.loadUsers(10);
expect(users).toHaveLength(10);
});
it('should load related data', async () => {
const { users, posts } = await loader.loadPostsWithAuthors(5);
expect(users).toHaveLength(2);
expect(posts).toHaveLength(5);
});
});

test/fixtures/data/users.json
[
{
"id": 1,
"name": "John Doe",
"email": "john@example.com",
"isActive": true,
"role": "admin"
},
{
"id": 2,
"name": "Jane Doe",
"email": "jane@example.com",
"isActive": true,
"role": "user"
},
{
"id": 3,
"name": "Bob Smith",
"email": "bob@example.com",
"isActive": false,
"role": "user"
}
]
// test/fixtures/data/posts.json
[
{
"id": 1,
"title": "First Post",
"content": "Content of first post",
"authorId": 1,
"isPublished": true
},
{
"id": 2,
"title": "Second Post",
"content": "Content of second post",
"authorId": 1,
"isPublished": true
}
]
test/fixtures/json-fixture-loader.ts
import * as fs from 'fs';
import * as path from 'path';
import { DataSource } from 'typeorm';
export class JsonFixtureLoader {
private fixturesPath: string;
constructor(
private dataSource: DataSource,
fixturesPath: string = path.join(__dirname, 'data'),
) {
this.fixturesPath = fixturesPath;
}
async load(entityName: string): Promise<any[]> {
const filePath = path.join(this.fixturesPath, `${entityName}.json`);
if (!fs.existsSync(filePath)) {
throw new Error(`Fixture file not found: ${filePath}`);
}
const data = JSON.parse(fs.readFileSync(filePath, 'utf8'));
const entity = this.getEntityByName(entityName);
return this.dataSource.getRepository(entity).save(data);
}
async loadAll(entityNames: string[]): Promise<Record<string, any[]>> {
const results: Record<string, any[]> = {};
for (const name of entityNames) {
results[name] = await this.load(name);
}
return results;
}
private getEntityByName(name: string): any {
const entityMap: Record<string, any> = {
users: User,
posts: Post,
comments: Comment,
};
return entityMap[name];
}
}
// Usage
describe('With JSON Fixtures', () => {
let loader: JsonFixtureLoader;
beforeEach(async () => {
loader = new JsonFixtureLoader(dataSource);
await loader.load('users');
await loader.load('posts');
});
it('should have loaded users', async () => {
const users = await userRepository.find();
expect(users).toHaveLength(3);
});
});

test/fixtures/faker.fixture.ts
import { faker } from '@faker-js/faker';
import { User } from '../../src/users/user.entity';
import { Post } from '../../src/posts/post.entity';
export class FakerFixture {
static createUser(overrides: Partial<User> = {}): User {
const user = new User();
user.name = overrides.name || faker.person.fullName();
user.email = overrides.email || faker.internet.email();
user.password = overrides.password || faker.internet.password();
user.isActive = overrides.isActive ?? faker.datatype.boolean();
user.role = overrides.role || faker.helpers.arrayElement(['user', 'admin', 'moderator']);
return user;
}
static createUsers(count: number, overrides: Partial<User> = {}): User[] {
return Array.from({ length: count }, () => this.createUser(overrides));
}
static createPost(overrides: Partial<Post> = {}): Post {
const post = new Post();
post.title = overrides.title || faker.lorem.sentence();
post.content = overrides.content || faker.lorem.paragraphs(3);
post.isPublished = overrides.isPublished ?? faker.datatype.boolean();
post.viewCount = overrides.viewCount || faker.number.int({ min: 0, max: 1000 });
return post;
}
static createPosts(count: number, overrides: Partial<Post> = {}): Post[] {
return Array.from({ length: count }, () => this.createPost(overrides));
}
static createPostWithAuthor(authorId?: number): { post: Post; author: User } {
const author = this.createUser();
const post = this.createPost();
post.authorId = authorId;
post.author = author;
return { post, author };
}
static createBlogScenario(options: {
userCount: number;
postsPerUser: number;
commentsPerPost: number;
}) {
const users = this.createUsers(options.userCount);
const posts: Post[] = [];
const comments: Comment[] = [];
users.forEach(user => {
const userPosts = this.createPosts(options.postsPerUser);
userPosts.forEach(post => {
post.author = user;
posts.push(post);
const postComments = Array.from({ length: options.commentsPerPost }, () => {
const comment = new Comment();
comment.content = faker.lorem.paragraph();
comment.post = post;
comment.author = users[faker.number.int({ min: 0, max: users.length - 1 })];
return comment;
});
comments.push(...postComments);
});
});
return { users, posts, comments };
}
}
// Usage
describe('With Faker', () => {
it('should create realistic test data', async () => {
const users = FakerFixture.createUsers(100);
const saved = await userRepository.save(users);
expect(saved).toHaveLength(100);
expect(saved[0].email).toMatch(/@/); // Valid email format
});
it('should create blog scenario', async () => {
const { users, posts, comments } = FakerFixture.createBlogScenario({
userCount: 10,
postsPerUser: 3,
commentsPerPost: 5,
});
await userRepository.save(users);
await postRepository.save(posts);
await commentRepository.save(comments);
expect(posts).toHaveLength(30); // 10 users * 3 posts
expect(comments).toHaveLength(150); // 30 posts * 5 comments
});
});

test/fixtures/test-state.fixture.ts
import { DataSource } from 'typeorm';
export class TestState {
private static instance: TestState;
private data: Map<string, any> = new Map();
private constructor() {}
static getInstance(): TestState {
if (!this.instance) {
this.instance = new TestState();
}
return this.instance;
}
set<T>(key: string, value: T): void {
this.data.set(key, value);
}
get<T>(key: string): T | undefined {
return this.data.get(key);
}
clear(): void {
this.data.clear();
}
}
export class TestStateFixture {
constructor(private dataSource: DataSource) {}
async setupAuthenticatedUser(): Promise<{ user: User; token: string }> {
const state = TestState.getInstance();
// Check if already created
let cached = state.get<{ user: User; token: string }>('authenticatedUser');
if (cached) {
return cached;
}
// Create new user
const user = UserFixture.create({
email: 'authenticated@test.com',
password: await bcrypt.hash('password123', 10),
});
const saved = await this.dataSource.getRepository(User).save(user);
// Generate token (simplified)
const token = 'generated-jwt-token';
const result = { user: saved, token };
state.set('authenticatedUser', result);
return result;
}
async setupAdminUser(): Promise<User> {
const state = TestState.getInstance();
let cached = state.get<User>('adminUser');
if (cached) {
return cached;
}
const admin = UserFixture.create({ role: 'admin' });
const saved = await this.dataSource.getRepository(User).save(admin);
state.set('adminUser', saved);
return saved;
}
}

Test Fixtures Quick Reference
+------------------------------------------------------------------+
| |
| Fixture Type | Use Case |
| -------------------|------------------------------------------|
| Simple factory | Basic entity creation |
| Builder pattern | Complex entity configuration |
| JSON fixtures | Static, version-controlled data |
| Faker fixtures | Dynamic, realistic data |
| State fixtures | Shared test state |
| |
| Best Practices | Description |
| -------------------|------------------------------------------|
| Consistent data | Same fixture produces similar data |
| Relationships | Support entity relationships |
| Reset state | Clear counters between tests |
| Reusable | Use across unit, integration, e2e |
| Maintainable | Centralized fixture definitions |
| |
+------------------------------------------------------------------+

Chapter 45: Test Database Management


Last Updated: February 2026