English | Русский
A library for creating TypeORM entity factories to simplify test data generation in NestJS applications.
When writing unit tests, you often need to create entity instances with populated data. Instead of manually creating objects in every test, factories allow you to:
- Centrally define test data structure
- Quickly generate realistic data using Faker
- Override specific fields when needed
- Create multiple instances with a single command
- Avoid code duplication across tests
pnpm add -D typeorm-factories @faker-js/faker
# or
npm install --save-dev typeorm-factories @faker-js/fakerThe library uses @faker-js/faker to generate fake data.
Create a factory file (e.g., factories/task.factory.ts):
import { faker } from '@faker-js/faker';
import { define } from 'typeorm-factories';
import { Task } from '../src/entities/task.entity';
define(Task, (fakerInstance) => {
const task = new Task();
task.id = fakerInstance.string.uuid();
task.title = fakerInstance.lorem.sentence();
task.description = fakerInstance.lorem.paragraph();
task.completed = fakerInstance.datatype.boolean();
task.createdAt = fakerInstance.date.past();
return task;
});import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { FactoryModule, factory } from 'typeorm-factories';
import { TasksService } from './tasks.service';
import { Task } from './entities/task.entity';
describe('TasksService', () => {
let service: TasksService;
let repository: Repository<Task>;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [FactoryModule],
providers: [
TasksService,
{
provide: getRepositoryToken(Task),
useValue: {
findOne: jest.fn(),
save: jest.fn(),
find: jest.fn(),
},
},
],
}).compile();
await module.init();
service = module.get<TasksService>(TasksService);
repository = module.get<Repository<Task>>(getRepositoryToken(Task));
});
describe('create', () => {
it('should create a new task', async () => {
const taskData = await factory(Task).make();
jest.spyOn(repository, 'save').mockResolvedValue(taskData);
const result = await service.create(taskData);
expect(result).toEqual(taskData);
expect(repository.save).toHaveBeenCalledWith(taskData);
});
});
describe('findCompleted', () => {
it('should return only completed tasks', async () => {
const completedTasks = await factory(Task)
.makeMany(3, { completed: true });
jest.spyOn(repository, 'find').mockResolvedValue(completedTasks);
const result = await service.findCompleted();
expect(result).toHaveLength(3);
expect(result.every(task => task.completed)).toBe(true);
});
});
});Registers a factory for an entity.
Parameters:
Entity: TypeORM entity classfactoryFunction: Function that receives a Faker instance and optional settings, returns a populated entity
define(User, (faker) => {
const user = new User();
user.email = faker.internet.email();
user.name = faker.person.fullName();
return user;
});Creates an EntityFactory instance for generating entity objects.
Parameters:
Entity: Entity classsettings(optional): Additional settings for the factory
Returns: EntityFactory<Entity, Settings>
Creates a single entity instance.
const task = await factory(Task).make();
// With field overrides
const urgentTask = await factory(Task).make({
priority: 'high',
dueDate: new Date('2024-12-31')
});Creates an array of entity instances.
// Create 5 tasks
const tasks = await factory(Task).makeMany(5);
// Create 3 tasks with "completed" status
const completedTasks = await factory(Task).makeMany(3, { completed: true });Applies a function to each created object. Useful for additional processing.
const tasksWithTimestamps = await factory(Task)
.map(async (task) => {
task.updatedAt = new Date();
return task;
})
.makeMany(5);Factory functions can be asynchronous for complex data generation:
define(User, async (faker) => {
const user = new User();
// Simulate async operations (API calls, file I/O, etc.)
user.email = await someAsyncEmailGenerator();
user.avatar = await fetchRandomAvatar();
user.name = faker.person.fullName();
return user;
});
// Works seamlessly with all methods
const user = await factory(User).make();
const users = await factory(User).makeMany(5);Generate unique sequential values for each entity:
define(User, (faker, settings, sequence) => {
const user = new User();
user.email = `user${sequence}@example.com`;
user.username = `user_${sequence}`;
user.name = faker.person.fullName();
return user;
});
// Creates users with emails: user0@, user1@, user2@
const users = await factory(User).makeMany(3);Define reusable modifications to your entities:
define(User, (faker) => {
const user = new User();
user.email = faker.internet.email();
user.role = 'user';
user.status = 'active';
return user;
})
.state('admin', (user) => {
user.role = 'admin';
user.permissions = ['read', 'write', 'delete'];
return user;
})
.state('suspended', (user) => {
user.status = 'suspended';
user.suspendedAt = new Date();
return user;
})
.state('premium', async (user) => {
user.subscriptionTier = 'premium';
user.subscriptionEndsAt = faker.date.future();
return user;
});
// Apply single state
const admin = await factory(User).state('admin').make();
// Apply multiple states
const suspendedAdmin = await factory(User)
.states(['admin', 'suspended'])
.make();Execute code before or after entity creation:
define(User, (faker) => {
const user = new User();
user.email = faker.internet.email();
user.password = 'plain-password';
return user;
})
.beforeMake(async (user) => {
// Hash password before creating
user.password = await bcrypt.hash(user.password, 10);
})
.afterMake(async (user) => {
// Log or perform additional setup
console.log('User created:', user.email);
});
const user = await factory(User).make();
// Password is automatically hashedAutomatically create related entities:
define(Post, (faker) => {
const post = new Post();
post.title = faker.lorem.sentence();
post.content = faker.lorem.paragraphs();
return post;
})
.association('author', User)
.association('comments', Comment, { count: 3 });
// Automatically creates a User and 3 Comments
const post = await factory(Post).make();
console.log(post.author); // User instance
console.log(post.comments); // Array of 3 Comment instancesCreate entities without using Faker (useful for mocks):
const userMock = factory(User).build({
id: '123',
email: 'test@example.com',
name: 'Test User',
});
// Returns a plain object without calling faker
expect(userMock.email).toBe('test@example.com');All features can be combined for complex scenarios:
define(User, (faker, settings, sequence) => {
const user = new User();
user.email = `user${sequence}@example.com`;
user.name = faker.person.fullName();
user.role = 'user';
return user;
})
.state('withPosts', async (user) => {
user.posts = await factory(Post).makeMany(5, { authorId: user.id });
return user;
})
.beforeMake(async (user) => {
user.createdAt = new Date();
})
.association('profile', UserProfile);
// Create an admin user with posts and profile
const admin = await factory(User)
.state('withPosts')
.make({ role: 'admin' });interface UserSettings {
role: 'admin' | 'user';
}
define(User, (faker, settings?: UserSettings) => {
const user = new User();
user.email = faker.internet.email();
user.name = faker.person.fullName();
user.role = settings?.role || 'user';
return user;
});
// Usage
const admin = await factory(User, { role: 'admin' }).make();
const regularUser = await factory(User, { role: 'user' }).make();Factories can automatically resolve nested entities:
define(Comment, (faker) => {
const comment = new Comment();
comment.text = faker.lorem.paragraph();
comment.author = factory(User).make(); // Returns a Promise
return comment;
});
// Nested entity will be automatically resolved
const comment = await factory(Comment).make();
console.log(comment.author); // User objectdefine(Post, (faker) => {
const post = new Post();
post.title = faker.lorem.sentence();
post.content = faker.lorem.paragraphs();
return post;
});
define(Comment, (faker) => {
const comment = new Comment();
comment.text = faker.lorem.paragraph();
return comment;
});
// Creating a post with comments
const post = await factory(Post).make();
const comments = await factory(Comment).makeMany(3, { postId: post.id });Reset sequence counters between tests to ensure consistent data:
import { resetSequences } from 'typeorm-factories';
describe('UserService', () => {
beforeEach(() => {
resetSequences(); // Reset all sequence counters
});
it('creates users with sequential emails', async () => {
const users = await factory(User).makeMany(2);
expect(users[0].email).toBe('user0@example.com');
expect(users[1].email).toBe('user1@example.com');
});
});It's recommended to keep factories in a separate directory:
your-project/
├── src/
│ ├── entities/
│ │ ├── user.entity.ts
│ │ └── task.entity.ts
│ └── ...
├── factories/
│ ├── user.factory.ts
│ └── task.factory.ts
└── test/
└── ...
The library automatically finds all files matching the pattern **/*.factory.{js,ts} when the module initializes.
- When
FactoryModuleis imported into a test module, it scans the project for factory files - All found factories are registered in a global registry
- The
factory()function retrieves the registered factory by entity class make()andmakeMany()methods use Faker to generate data- Nested factories and promises are automatically resolved
- TypeORM: ^0.3.0
- NestJS: ^11.0.0
- @faker-js/faker: ^10.0.0
- Node.js: >=18.0.0
factories/task.factory.ts:
import { faker } from '@faker-js/faker';
import { define } from 'typeorm-factories';
import { Task, TaskStatus } from '../src/entities/task.entity';
define(Task, (fakerInstance) => {
const task = new Task();
task.id = fakerInstance.string.uuid();
task.title = fakerInstance.lorem.sentence({ min: 3, max: 8 });
task.description = fakerInstance.lorem.paragraph();
task.status = fakerInstance.helpers.arrayElement([
TaskStatus.TODO,
TaskStatus.IN_PROGRESS,
TaskStatus.DONE
]);
task.priority = fakerInstance.number.int({ min: 1, max: 5 });
task.dueDate = fakerInstance.date.future();
task.createdAt = fakerInstance.date.past();
task.updatedAt = new Date();
return task;
});test/tasks.service.spec.ts:
import { Test } from '@nestjs/testing';
import { FactoryModule, factory } from 'typeorm-factories';
import { Task } from '../src/entities/task.entity';
describe('TasksService', () => {
beforeAll(async () => {
const module = await Test.createTestingModule({
imports: [FactoryModule],
// ... other providers
}).compile();
await module.init();
});
it('example test', async () => {
const task = await factory(Task).make({ priority: 5 });
expect(task.priority).toBe(5);
});
});If you have questions or issues, please create an Issue in the project repository.
MIT