When writing unit tests for a NestJS application using Jest, effectively managing test data is crucial for ensuring reliability, maintainability, and isolation. In this guide, we'll cover how to organize, create, and utilize test data in NestJS projects following best practices.
✅ 1. Creating Test Data
Factory Functions (Dynamic Data Generation)
Factory functions allow you to create reusable mock data tailored for different test cases.
// src/collection-orders/__mocks__/collectionOrder.factory.ts
import { Types } from 'mongoose';
export const createMockCollectionOrder = (overrides = {}) => ({
_id: new Types.ObjectId(),
collectionVolume: 10,
pickupRequestDate: new Date(),
deliveryMan: new Types.ObjectId(),
retailStore: new Types.ObjectId(),
status: 'booking',
...overrides, // Allow custom overrides
});
Fixtures (Static Data)
Fixtures are great for static, unchanging data that’s reused across multiple tests.
// src/collection-orders/__mocks__/collectionOrder.fixture.ts
export const collectionOrderFixture = {
_id: '60b8f9f4e8d1b542f0e4c9a5',
collectionVolume: 20,
pickupRequestDate: '2023-01-01T00:00:00Z',
status: 'completed',
requestMemo: 'Handle with care',
};
๐ฆ 2. Organizing Test Data
Folder Structure
src/
└── collection-orders/
├── dto/
├── __mocks__/ # Test mocks & fixtures
│ ├── collectionOrder.factory.ts
│ └── collectionOrder.fixture.ts
├── collection-orders.controller.ts
├── collection-orders.controller.spec.ts
├── collection-orders.service.ts
└── collection-orders.service.spec.ts
Purpose of Each Folder:
__mocks__/
: Stores mock data (factories and fixtures).*.spec.ts
: Jest unit test files.dto/
: Data Transfer Objects used in the application.
๐งช 3. Using Test Data in Jest
Example: Service Unit Test
// src/collection-orders/collection-orders.service.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { CollectionOrdersService } from './collection-orders.service';
import { getModelToken } from '@nestjs/mongoose';
import { CollectionOrder } from '@sildeswj/thechium-schema';
import { createMockCollectionOrder, collectionOrderFixture } from './__mocks__/collectionOrder.factory';
describe('CollectionOrdersService', () => {
let service: CollectionOrdersService;
let model: any;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
CollectionOrdersService,
{
provide: getModelToken(CollectionOrder.name),
useValue: {
create: jest.fn(),
findById: jest.fn(),
},
},
],
}).compile();
service = module.get<CollectionOrdersService>(CollectionOrdersService);
model = module.get(getModelToken(CollectionOrder.name));
});
it('should create a collection order', async () => {
const mockOrder = createMockCollectionOrder({ collectionVolume: 50 });
model.create.mockResolvedValue(mockOrder);
const result = await service.create(mockOrder);
expect(result.collectionVolume).toBe(50);
expect(model.create).toHaveBeenCalledWith(mockOrder);
});
it('should return a fixed collection order from fixture', async () => {
model.findById.mockResolvedValue(collectionOrderFixture);
const result = await service.findOne('60b8f9f4e8d1b542f0e4c9a5');
expect(result.status).toBe('completed');
expect(result.collectionVolume).toBe(20);
});
});
๐️ 4. Using In-Memory Databases (For Integration Tests)
For integration tests that require database interaction, consider using in-memory databases.
Example: MongoDB Memory Server
// src/collection-orders/__tests__/mongodb-memory.spec.ts
import { MongoMemoryServer } from 'mongodb-memory-server';
import mongoose from 'mongoose';
let mongoServer: MongoMemoryServer;
beforeAll(async () => {
mongoServer = await MongoMemoryServer.create();
const uri = mongoServer.getUri();
await mongoose.connect(uri);
});
afterAll(async () => {
await mongoose.disconnect();
await mongoServer.stop();
});
Why Use In-Memory Databases?
- Fast: No need for external database setups.
- Isolated: Keeps tests independent of production data.
- Reliable: Clean state for each test run.
๐ 5. Best Practices for Test Data Management
- Isolation: Each test should create its own data to avoid dependencies.
- Cleanup: Use
afterEach
to clear test data when necessary. - Reusability: Utilize factories for dynamic data and fixtures for static data.
- Mock External Services: Use Jest mocks for APIs, databases, and external services.
- Consistency: Store all test-related data within
__mocks__
for easy access and maintenance.
Final Thoughts
Managing test data effectively is key to writing clean, reliable, and maintainable tests in NestJS. By using factories, fixtures, and in-memory databases, you can ensure that your tests remain isolated, consistent, and efficient.
What strategies do you use for managing test data in your NestJS projects? Share your thoughts in the comments below! ๐