Comprehensive Guide to Jest Implementation in a NestJS with GraphQL and MongoDB

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
  2. Setting Up Jest in a NestJS Project
  3. Writing Unit Tests
  4. Writing Integration Tests
  5. Writing End-to-End (E2E) Tests
  6. Advanced Jest Features
  7. Best Practices for Testing in NestJS
  8. Summarize layout
  9. Conclusion


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:

  • Zero-configuration setup: Jest works out of the box with minimal configuration.
  • Snapshot testing: Capture the state of your UI or data structures to ensure they don’t change unexpectedly.
  • Mocking: Easily mock dependencies, modules, and functions.
  • Code coverage: Generate detailed reports on how much of your code is covered by tests.

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.

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:

Article content
IMG: coverage report


7. Best Practices for Testing in NestJS

  • Write small, focused tests: Each test should verify a single behavior.
  • Use mocking: Mock dependencies to isolate the unit of code being tested.
  • Test edge cases: Ensure your tests cover edge cases and error scenarios.
  • Keep tests readable: Use descriptive test names and organize tests logically.
  • Run tests frequently: Integrate tests into your development workflow


8. Summarize layout

to summarize above concepts here is the textual representation of layout to understand its layout and workings :


Article content
IMG: Layout of nestjs and jest

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! 🚀

To view or add a comment, sign in

Others also viewed

Explore content categories