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.
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();
};
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!
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);
});
});
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With