We've all been there. You're writing tests for your NestJS application, and before you can even test the actual business logic, you need to create a bunch of entity instances. So you write something like this:
it('should update user profile', async () => {
const user = new User();
user.id = 'some-uuid';
user.email = 'test@example.com';
user.name = 'John Doe';
user.role = 'user';
user.status = 'active';
user.createdAt = new Date();
user.updatedAt = new Date();
// Finally, the actual test logic...
const result = await service.updateProfile(user.id, { name: 'Jane Doe' });
expect(result.name).toBe('Jane Doe');
});
Now multiply this by 50 tests, and you're looking at hundreds of lines of repetitive setup code. Not to mention when your entity schema changes, you'll need to update every single test.
There has to be a better way, right?
The Factory Pattern to the Rescue
The factory pattern is well-established in the Rails community (FactoryBot) and Laravel (Factory classes), but it's been surprisingly underutilized in the TypeScript ecosystem. That's what led me to create and recently modernize typeorm-factories — a library that brings the power of test data factories to NestJS and TypeORM applications.
Let me show you how it transforms your testing workflow.
Getting Started (The Basics)
First, install the package:
pnpm add -D typeorm-factories @faker-js/faker
Now, instead of manually creating entities, you define a factory once:
// factories/user.factory.ts
import { faker } from '@faker-js/faker';
import { define } from 'typeorm-factories';
import { User } from '../entities/user.entity';
define(User, (faker) => {
const user = new User();
user.id = faker.string.uuid();
user.email = faker.internet.email();
user.name = faker.person.fullName();
user.role = 'user';
user.status = 'active';
return user;
});
And use it in your tests:
import { factory } from 'typeorm-factories';
it('should update user profile', async () => {
const user = await factory(User).make();
const result = await service.updateProfile(user.id, { name: 'Jane Doe' });
expect(result.name).toBe('Jane Doe');
});
That's it. One line instead of eight. But the real magic happens when you need variations.
Overriding Fields When You Need To
Sometimes you need specific values for your test:
it('should not allow suspended users to post', async () => {
const suspendedUser = await factory(User).make({
status: 'suspended'
});
await expect(service.createPost(suspendedUser.id, postData))
.rejects
.toThrow('User is suspended');
});
The factory generates all the required fields with realistic data, but you can override any field you care about for your specific test case.
Creating Multiple Entities
Need test data in bulk? No problem:
it('should return paginated user list', async () => {
await factory(User).makeMany(25);
const page1 = await service.getUsers({ page: 1, limit: 10 });
const page2 = await service.getUsers({ page: 2, limit: 10 });
expect(page1.items).toHaveLength(10);
expect(page2.items).toHaveLength(10);
});
You can even override fields for all created entities:
const admins = await factory(User).makeMany(5, { role: 'admin' });
The Game Changers: Advanced Features
This is where things get interesting. In version 2.0, I added several features that solve real problems I've encountered in production codebases.
1. Sequences for Unique Values
Ever needed guaranteed unique emails or usernames in your tests? Sequences solve this elegantly:
define(User, (faker, settings, sequence) => {
const user = new User();
user.email = `user${sequence}@test.com`;
user.username = `user_${sequence}`;
user.name = faker.person.fullName();
return user;
});
const users = await factory(User).makeMany(3);
// users[0].email = 'user0@test.com'
// users[1].email = 'user1@test.com'
// users[2].email = 'user2@test.com'
The sequence counter auto-increments for each entity. Clean, predictable, and no duplicate key errors in your tests.
2. States for Different Scenarios
Let's say you have different user types in your app. Instead of creating separate factories or remembering which fields to override, define states:
define(User, (faker) => {
const user = new User();
user.email = faker.internet.email();
user.name = faker.person.fullName();
user.role = 'user';
user.emailVerified = false;
return user;
})
.state('admin', (user) => {
user.role = 'admin';
user.permissions = ['read', 'write', 'delete', 'admin'];
return user;
})
.state('verified', (user) => {
user.emailVerified = true;
user.emailVerifiedAt = new Date();
return user;
});
Now creating test users is expressive and readable:
const regularUser = await factory(User).make();
const admin = await factory(User).state('admin').make();
const verifiedAdmin = await factory(User).states(['admin', 'verified']).make();
Your test clearly communicates what kind of user it's working with.
3. Lifecycle Hooks for Complex Setup
What if you need to hash passwords or generate computed fields? Lifecycle hooks have you covered:
import * as bcrypt from 'bcrypt';
define(User, (faker) => {
const user = new User();
user.email = faker.internet.email();
user.password = 'password123'; // Plain password
user.name = faker.person.fullName();
return user;
})
.beforeMake(async (user) => {
// Hash password before entity is created
user.password = await bcrypt.hash(user.password, 10);
})
.afterMake(async (user) => {
// Log creation for debugging
console.log(`Created user: ${user.email}`);
});
const user = await factory(User).make();
// Password is automatically hashed!
This is especially useful when your entity has computed fields or needs specific transformations that you don't want to repeat in every test.
4. Associations for Related Entities
Here's a real pain point: creating entities with relationships. Normally you'd need to create a user, then create posts, then link them together. With associations:
define(Post, (faker) => {
const post = new Post();
post.title = faker.lorem.sentence();
post.content = faker.lorem.paragraphs(3);
return post;
})
.association('author', User)
.association('comments', Comment, { count: 5 });
const post = await factory(Post).make();
// post.author is a User instance
// post.comments is an array of 5 Comment instances
The factory automatically creates all the related entities. This turns what could be 10+ lines of setup code into a single method call.
5. Async Factory Functions
Sometimes you need to do async operations during entity creation — maybe fetching data from an external service or performing database lookups:
define(User, async (faker) => {
const user = new User();
user.email = faker.internet.email();
user.name = faker.person.fullName();
// Simulate fetching avatar from external API
user.avatarUrl = await fetchRandomAvatar();
return user;
});
This works seamlessly with all other features — states, hooks, associations, everything.
Real-World Example
Let me show you how this all comes together in a realistic scenario. Imagine you're testing a blog platform:
// factories/user.factory.ts
define(User, (faker, settings, sequence) => {
const user = new User();
user.id = sequence;
user.email = `user${sequence}@blog.com`;
user.username = faker.internet.userName();
user.name = faker.person.fullName();
user.bio = faker.lorem.paragraph();
user.role = 'author';
user.status = 'active';
return user;
})
.state('withPosts', async (user) => {
user.posts = await factory(Post).makeMany(5, { authorId: user.id });
user.postCount = 5;
return user;
})
.state('featured', (user) => {
user.featured = true;
user.featuredAt = new Date();
return user;
})
.beforeMake(async (user) => {
user.slug = user.username.toLowerCase().replace(/[^a-z0-9]/g, '-');
});
// In your tests
describe('Blog Service', () => {
beforeEach(() => {
resetSequences(); // Start from 0 for each test
});
it('should display featured authors on homepage', async () => {
const featuredAuthors = await factory(User)
.states(['withPosts', 'featured'])
.makeMany(3);
const homepage = await service.getHomepage();
expect(homepage.featuredAuthors).toHaveLength(3);
expect(homepage.featuredAuthors[0].postCount).toBeGreaterThan(0);
});
it('should only allow active authors to publish', async () => {
const suspendedAuthor = await factory(User).make({
status: 'suspended'
});
await expect(service.publishPost(suspendedAuthor.id, postData))
.rejects
.toThrow('Author is suspended');
});
});
Notice how expressive the tests are. You can immediately understand what's being tested without wading through entity creation code.
Integration with NestJS
Since this is designed for NestJS applications, integration is straightforward:
import { Test, TestingModule } from '@nestjs/testing';
import { FactoryModule } from 'typeorm-factories';
describe('UserService', () => {
let service: UserService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [FactoryModule], // Import the module
providers: [UserService, ...],
}).compile();
await module.init(); // Important: Initialize the module
service = module.get<UserService>(UserService);
});
// Your tests...
});
The FactoryModule automatically discovers and loads your factory definitions, so you don't need to manually import them in every test file.
Build Method for Simple Mocks
Sometimes you don't need Faker at all — you just want a simple object with specific values. The build() method is perfect for this:
const userMock = factory(User).build({
id: 1,
email: 'admin@example.com',
role: 'admin',
name: 'Admin User'
});
jest.spyOn(repository, 'findOne').mockResolvedValue(userMock);
This creates a plain object without running faker or any hooks. It's faster and more predictable for simple mock scenarios.
Testing Tips
Here are some patterns I've found helpful:
Reset sequences between tests to ensure predictable data:
beforeEach(() => {
resetSequences();
});
Use states to make tests self-documenting:
// Instead of this:
const user = await factory(User).make({
role: 'admin',
emailVerified: true,
status: 'active'
});
// Do this:
const user = await factory(User).states(['admin', 'verified']).make();
Combine with Jest mocks for isolated unit tests:
it('should send email to new users', async () => {
const user = await factory(User).make();
const emailSpy = jest.spyOn(emailService, 'send');
await service.createUser(user);
expect(emailSpy).toHaveBeenCalledWith(user.email, expect.any(String));
});
Conclusion
Testing shouldn't feel like a chore. With the right tools, it can actually be enjoyable (or at least less painful). typeorm-factories eliminates the boilerplate and lets you focus on what matters: writing good tests for your business logic.
If you're tired of copying and pasting entity creation code across your test suite, give it a try:
pnpm add -D typeorm-factories @faker-js/faker
Check out the GitHub repository for full documentation and examples.
Happy testing! 🎉
What's your approach to test data generation? Do you use factories, builders, or something else? Let me know in the comments!
Top comments (0)