Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How To Setup MongoDB Memory Server In NestJS for E2E Testing?

What I want

I'm looking to integrate mongodb-memory-server in my E2E testing within NestJS. I noticed that I need a mongodb instance to work with supertest, but I would like to not depend on an external database and would rather have the mongodb server in-memory.

Problem

I tried setting up mongodb-memory-server in my E2E test suite, but a couple of my tests it getting back a 500 Internal Server Error when the expected status code is supposed to be 201. Here is my code:

drive.e2e-spec.ts

import { HttpStatus, INestApplication } from '@nestjs/common';
import { getModelToken } from '@nestjs/mongoose';
import { Test, TestingModule } from '@nestjs/testing';
import { Types, Model } from 'mongoose';
import * as request from 'supertest';

import { AppModule } from '#app.module';
import { CreateDriveDto } from '#drive/dto/create-drive.dto';
import { Drive, DriveDoc } from '#drive/schema/drive.schema';
import {
  closeInMongodConnection,
  rootMongooseTestModule,
} from 'test/util/MongooseTestModule';
import { dataFromJoi } from 'test/util/generate-valid-sample-data';
import { configTestApp } from 'test/util/setup-test-app';

const route = `/api/test/v2/drive`;

// generate valid sample data from Joi schema
const validCreateData = dataFromJoi(CreateDriveDto);

let moduleFixture: TestingModule;
let app: INestApplication;
let mockDriveModel: Model<DriveDoc>;

beforeAll(async () => {
  moduleFixture = await Test.createTestingModule({
    imports: [AppModule, rootMongooseTestModule()],
  }).compile();

  app = moduleFixture.createNestApplication();
  mockDriveModel = moduleFixture.get<Model<DriveDoc>>(
    getModelToken(Drive.name),
  );
  configTestApp(app);
  await app.init();
});

afterEach(async () => {
  await closeInMongodConnection();
});

afterAll(async () => {
  await app.close();
});

describe(`Integration Test :: Route ${route}`, () => {
  describe(`POST`, () => {
    test('Return 400 if request is invalid', async () => {
      await request(app.getHttpServer())
        .post(route)
        .send({})
        .expect(HttpStatus.BAD_REQUEST)
        .then((res) => {
          expect(res.body).toHaveProperty('statusCode');
          expect(res.body).toHaveProperty('message');
        });
    });

    test('Return 201 and create new entry if request is valid', async () => {
      await request(app.getHttpServer())
        .post(route)
        .send(validCreateData)
        .expect(HttpStatus.CREATED);
    });
  });

  describe(`GET`, () => {
    test('Return 200 and an array', async () => {
      const doc = await mockDriveModel.create(validCreateData);
      await request(app.getHttpServer())
        .get(route)
        .expect(HttpStatus.OK)
        .then((res) => {
          expect(Array.isArray(res.body)).toBeTruthy();
        });
    });
  });
});

MongooseTestModule.ts

import { MongooseModule, MongooseModuleOptions } from '@nestjs/mongoose';
import { MongoMemoryServer } from 'mongodb-memory-server';
import { disconnect } from 'mongoose';

let mongod: MongoMemoryServer;

export const rootMongooseTestModule = (options: MongooseModuleOptions = {}) =>
  MongooseModule.forRootAsync({
    useFactory: async () => {
      mongod = await MongoMemoryServer.create();
      const mongoUri = mongod.getUri();
      return {
        uri: mongoUri,
        ...options,
      };
    },
  });

export const closeInMongodConnection = async () => {
  await disconnect();
  if (mongod) await mongod.stop();
};

Discussion

The implementation that should be focused on is the rootMongoseTestModule, which is where mongodb-memory-server would be instantiated. Setup doesn't seem to work. Any thoughts or advice? I am also open to other testing strategies as well. Thanks in advance!

like image 239
Nathan Bernardo Avatar asked Sep 18 '25 08:09

Nathan Bernardo


1 Answers

For anyone who is looking for a simpler and cleaner solution. Here is how I integrated mongo-memory-server in nest.js

Create a test-db.module.ts file in ./test/ directory.

import { MongooseModule } from '@nestjs/mongoose';
import { MongoMemoryServer } from 'mongodb-memory-server';
import { ConfigModule, ConfigService } from '@nestjs/config';

let mongod: MongoMemoryServer;

export const TestDbModule = MongooseModule.forRootAsync({
  imports: [ConfigModule],
  useFactory: async (configService: ConfigService) => {
    mongod = await MongoMemoryServer.create();
    const mongoUri = mongod.getUri();
    return {
      uri: mongoUri,
    };
  },
  inject: [ConfigService],
});

export const closeInMongodConnection = async () => {
  if (mongod) await mongod.stop();
};

Update the jest config to be

{
   "moduleFileExtensions": ["js", "json", "ts"],
   "rootDir": ".",
   "testEnvironment": "node",
   "testRegex": ".e2e-spec.ts$",
   "transform": {
   "^.+\\.(t|j)s$": "ts-jest"
},
   "moduleNameMapper": {
     "^src/(.*)$": "<rootDir>/$1"
   }
}

Lastly import the TestDbModule and related Code in your spec file.

import { Test, TestingModule } from '@nestjs/testing';
import { MongooseModule, getModelToken } from '@nestjs/mongoose';
import { TemplatesService } from './templates.service';
import { CreateTemplateDto } from './dto/create-template.dto';
import { Template, TemplateSchema, TemplateType } from './templates.schema';
import {
  BadRequestException,
  ForbiddenException,
  NotFoundException,
} from '@nestjs/common';
import { HelperService } from 'src/common/services/helper.service';
import mongoose, { Model } from 'mongoose';
import {
  TestDbModule,
  closeInMongodConnection,
} from '../../test/test-db.module';
import { UpdateTemplateDto } from './dto/update-template.dto';

const getTemplateDto = ({ name, type }): CreateTemplateDto => {
  const createTemplateDto: CreateTemplateDto = new CreateTemplateDto();
  createTemplateDto.name = name || 'Test Template';
  createTemplateDto.type = type;
  createTemplateDto.addedBy = new mongoose.Types.ObjectId();

  if (type === TemplateType.EMAIL) {
    createTemplateDto.subject = 'Test Subject';
    createTemplateDto.html = '<p>Test HTML</p>';
  } else {
    createTemplateDto.text = 'Test SMS';
  }

  return createTemplateDto;
};

describe('TemplatesService', () => {
  let service: TemplatesService;
  let templateModel: Model<Template>;

  beforeAll(async () => {});

  afterAll(async () => await closeInMongodConnection());

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      imports: [
        TestDbModule,
        MongooseModule.forFeature([
          { name: 'Template', schema: TemplateSchema },
        ]),
      ],
      providers: [TemplatesService, HelperService],
    }).compile();

    service = module.get<TemplatesService>(TemplatesService);
    templateModel = module.get<Model<Template>>    (getModelToken(Template.name));
  });

  afterEach(async () => {
    // Clear the templates collection after each test
    await templateModel.deleteMany({});
  });

  it('service should be defined', () => {
    expect(service).toBeDefined();
  });

  describe('create', () => {
    it('should create a template', async () => {
      const createTemplateDto = getTemplateDto({
        name: null,
        type: TemplateType.EMAIL,
      });

      const result = await service.create(createTemplateDto);

      expect(result.name).toEqual(createTemplateDto.name);
      expect(result.type).toEqual(createTemplateDto.type);
      expect(result.subject).toEqual(createTemplateDto.subject);
      expect(result.html).toEqual(createTemplateDto.html);
    });
  });
});
like image 149
Ussama Zubair Avatar answered Sep 21 '25 00:09

Ussama Zubair