Comprehensive Guide to Jest Implementation in a NestJS with GraphQL and MongoDB
In this blog, we will dive deep into the implementation of Jest in a NestJS backend application that uses GraphQL and MongoDB. We will cover everything from setting up Jest in a NestJS project to writing unit tests, integration tests, and end-to-end (E2E) tests. Additionally, we will explore various use cases and examples to help you understand how to integrate Jest into your application effectively.
Table of Contents
1. Introduction to Jest and NestJS
What is Jest?
Jest is a JavaScript testing framework developed by Facebook. It is widely used for testing JavaScript applications, including Node.js backend applications. Jest provides a rich set of features, including:
What is NestJS?
NestJS is a progressive Node.js framework for building efficient, reliable, and scalable server-side applications. It is heavily inspired by Angular and uses TypeScript by default. NestJS supports various technologies, including GraphQL and MongoDB, making it a popular choice for modern backend development.
2. Setting Up Jest in a NestJS Project
NestJS comes with built-in support for Jest. When you create a new NestJS project, Jest is already configured for you. However, let’s ensure everything is set up correctly.
Installation
If Jest is not already installed, you can add it using the following command:
npm install --save-dev @nestjs/testing jest ts-jest @types/jest
Configuration
NestJS automatically generates a jest.config.js file in the root of your project. This file contains the default Jest configuration. You can customize it as needed:
// jest.config.js
module.exports = {
moduleFileExtensions: ['js', 'json', 'ts'],
rootDir: 'src',
testRegex: '.*\\.spec\\.ts$',
transform: {
'^.+\\.(t|j)s$': 'ts-jest',
},
collectCoverageFrom: ['**/*.(t|j)s'],
coverageDirectory: '../coverage',
testEnvironment: 'node',
};
Folder Structure
Organize your tests in the src folder alongside your application code. For example:
src/
users/
users.service.ts
users.service.spec.ts
app.controller.ts
app.controller.spec.ts
3. Writing Unit Tests
Unit tests focus on testing individual units of code in isolation. In NestJS, this typically includes services, resolvers, and repositories.
Testing Services
Services contain the core business logic of your application. Let’s write a unit test for a UsersService.
Example: users.service.ts
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { User } from './schemas/user.schema';
@Injectable()
export class UsersService {
constructor(@InjectModel(User.name) private userModel: Model<User>) {}
async findAll(): Promise<User[]> {
return this.userModel.find().exec();
}
}
Example: users.service.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { UsersService } from './users.service';
import { getModelToken } from '@nestjs/mongoose';
import { Model } from 'mongoose';
describe('UsersService', () => {
let service: UsersService;
let model: Model<User>;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
UsersService,
{
provide: getModelToken(User.name),
useValue: {
find: jest.fn().mockResolvedValue([{ name: 'John Doe' }]),
},
},
],
}).compile();
service = module.get<UsersService>(UsersService);
model = module.get<Model<User>>(getModelToken(User.name));
});
it('should be defined', () => {
expect(service).toBeDefined();
});
it('should return all users', async () => {
const result = await service.findAll();
expect(result).toEqual([{ name: 'John Doe' }]);
expect(model.find).toHaveBeenCalled();
});
});
Testing Resolvers (GraphQL)
Resolvers handle GraphQL queries and mutations. Let’s write a unit test for a UsersResolver.
Example: users.resolver.ts
import { Resolver, Query } from '@nestjs/graphql';
import { UsersService } from './users.service';
import { User } from './schemas/user.schema';
@Resolver(() => User)
export class UsersResolver {
constructor(private readonly usersService: UsersService) {}
@Query(() => [User])
async users(): Promise<User[]> {
return this.usersService.findAll();
}
}
Example: users.resolver.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { UsersResolver } from './users.resolver';
import { UsersService } from './users.service';
describe('UsersResolver', () => {
let resolver: UsersResolver;
let service: UsersService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
UsersResolver,
{
provide: UsersService,
useValue: {
findAll: jest.fn().mockResolvedValue([{ name: 'John Doe' }]),
},
},
],
}).compile();
resolver = module.get<UsersResolver>(UsersResolver);
service = module.get<UsersService>(UsersService);
});
it('should be defined', () => {
expect(resolver).toBeDefined();
});
it('should return all users', async () => {
const result = await resolver.users();
expect(result).toEqual([{ name: 'John Doe' }]);
expect(service.findAll).toHaveBeenCalled();
});
});
Testing Repositories (MongoDB)
Repositories interact with the database. Let’s write a unit test for a MongoDB repository.
Example: users.repository.ts
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { User } from './schemas/user.schema';
@Injectable()
export class UsersRepository {
constructor(@InjectModel(User.name) private userModel: Model<User>) {}
async findAll(): Promise<User[]> {
return this.userModel.find().exec();
}
}
Example: users.repository.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { UsersRepository } from './users.repository';
import { getModelToken } from '@nestjs/mongoose';
import { Model } from 'mongoose';
describe('UsersRepository', () => {
let repository: UsersRepository;
let model: Model<User>;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
UsersRepository,
{
provide: getModelToken(User.name),
useValue: {
find: jest.fn().mockResolvedValue([{ name: 'John Doe' }]),
},
},
],
}).compile();
repository = module.get<UsersRepository>(UsersRepository);
model = module.get<Model<User>>(getModelToken(User.name));
});
it('should be defined', () => {
expect(repository).toBeDefined();
});
it('should return all users', async () => {
const result = await repository.findAll();
expect(result).toEqual([{ name: 'John Doe' }]);
expect(model.find).toHaveBeenCalled();
});
});
4. Writing Integration Tests
Integration tests ensure that different parts of your application work together correctly. In NestJS, this typically involves testing GraphQL API endpoints and MongoDB interactions.
Recommended by LinkedIn
Testing GraphQL API Endpoints
Let’s write an integration test for a GraphQL query.
Example: users.e2e-spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
import { UsersResolver } from '../src/users/users.resolver';
import { UsersService } from '../src/users/users.service';
import * as request from 'supertest';
describe('UsersResolver (e2e)', () => {
let app: INestApplication;
beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
autoSchemaFile: true,
}),
],
providers: [UsersResolver, UsersService],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
afterEach(async () => {
await app.close();
});
it('should return all users', () => {
return request(app.getHttpServer())
.post('/graphql')
.send({
query: '{ users { name } }',
})
.expect(200)
.expect(({ body }) => {
expect(body.data.users).toEqual([{ name: 'Nikesh Dahal' }]);
});
});
});
Testing MongoDB Interactions
Let’s write an integration test for MongoDB interactions.
Example: users.repository.e2e-spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { MongooseModule } from '@nestjs/mongoose';
import { UsersRepository } from '../src/users/users.repository';
import { User, UserSchema } from '../src/users/schemas/user.schema';
import { rootMongooseTestModule } from './utils/test-mongoose-module';
import { closeInMongodConnection } from './utils/mongodb-memory-server';
describe('UsersRepository (e2e)', () => {
let repository: UsersRepository;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [
rootMongooseTestModule(),
MongooseModule.forFeature([{ name: User.name, schema: UserSchema }]),
],
providers: [UsersRepository],
}).compile();
repository = module.get<UsersRepository>(UsersRepository);
});
afterEach(async () => {
await closeInMongodConnection();
});
it('should return all users', async () => {
await repository.create({ name: 'Nikesh Dahal' });
const result = await repository.findAll();
expect(result).toEqual([{ name: 'Nikesh Dahal' }]);
});
});
5. Writing End-to-End (E2E) Tests
E2E tests simulate real user scenarios by testing the entire application from start to finish. Let’s write an E2E test for a GraphQL API.
Example: app.e2e-spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from '../src/app.module';
describe('AppController (e2e)', () => {
let app: INestApplication;
beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
afterEach(async () => {
await app.close();
});
it('/ (GET)', () => {
return request(app.getHttpServer())
.get('/')
.expect(200)
.expect('Hello World!');
});
});
6. Advanced Jest Features
this is the most interesting and beautiful portion
Mocking Dependencies
Jest allows you to mock dependencies to isolate the unit of code being tested.
Example: Mocking a Service
jest.mock('./users.service', () => ({
UsersService: jest.fn().mockImplementation(() => ({
findAll: jest.fn().mockResolvedValue([{ name: 'John Doe' }]),
})),
}));
Snapshot Testing
Snapshot testing captures the output of a component or function and compares it to a stored snapshot.
Example: Snapshot Testing
it('should match snapshot', () => {
const result = service.findAll();
expect(result).toMatchSnapshot();
});
Code Coverage
Jest can generate code coverage reports to show how much of your code is covered by tests.
Example: Generating Coverage
Add the following script to your package.json:
"scripts": {
"test:coverage": "jest --coverage"
}
Run the script:
npm run test:coverage
with the --coverage flag, it will generate a code coverage report. The output will look like this:
7. Best Practices for Testing in NestJS
8. Summarize layout
to summarize above concepts here is the textual representation of layout to understand its layout and workings :
8. Conclusion
Testing is a critical part of building reliable and maintainable applications. By leveraging Jest in your NestJS application, you can ensure that your GraphQL APIs and MongoDB interactions work as expected. This guide covered everything from setting up Jest to writing unit tests, integration tests, and E2E tests. With these tools and techniques, you can confidently build and maintain high-quality backend applications.
Happy testing! 🚀