Assuming you already installed and configured mongoose in your NestJS project.
For the sake of having something to test we will create a Squid API. The API will provide a random squid gif when called.
You can see the actual implementation in the demo repo.
Writing tests for code that interact with databases is rather painful.
You either have to create test databases and delete them afterward.
OR
You end up writing and debugging a ton of code to clean before after the testing...
Today is the end of your misery!
I am here to save you the trouble of testing. with nestJS, mongoose and MongoDB.... sorry for the others
First, we will need to add a new development package to the project. (link to the Github repository provided at the end of this article)
npm i --save-dev mongodb-memory-server
Cool, We can now spawn mongo daemon in memory! How awesome is that?
Since I am a lazy brat, I do not want to rewrite the in-memory mongod bootstrapping code.
Let's write a small test utils file that will provide us an easy to import preconfigured root MongooseModule and an helper to close the connection.
import { MongooseModule, MongooseModuleOptions } from '@nestjs/mongoose';
import { MongoMemoryServer } from 'mongodb-memory-server';
let mongod: MongoMemoryServer;
export const rootMongooseTestModule = (options: MongooseModuleOptions = {}) => MongooseModule.forRootAsync({
useFactory: async () => {
mongod = new MongoMemoryServer();
const mongoUri = await mongod.getUri();
return {
uri: mongoUri,
...options,
}
},
});
export const closeInMongodConnection = async () => {
if (mongod) await mongod.stop();
}
Excellent, in-memory plug an play MongoDB daemon!
Let's import that bad boy to our service and controller test.
Don't forget to close the connection in the afterAll
function.
import { Test, TestingModule } from '@nestjs/testing';
import { MongooseModule } from '@nestjs/mongoose';
import { SquidService } from './squid.service';
import { closeInMongodConnection, rootMongooseTestModule } from '../test-utils/mongo/MongooseTestModule';
import { SquidSchema } from './model/squid.schema';
describe('SquidService', () => {
let service: SquidService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [
rootMongooseTestModule(),
MongooseModule.forFeature([{ name: 'Squid', schema: SquidSchema }]),
],
providers: [SquidService],
}).compile();
service = module.get<SquidService>(SquidService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
/**
Write meaningful test
**/
afterAll(async () => {
await closeInMongodConnection();
});
});
And voila!
You are all set.
Go back to testing the wonderful code you are writing!
Next Time we will handle the case of end to end test for NestJS.
Sources
NestjJS
NestJS techniques mongodb
mongod-in-memory
The issue that saved me
Top comments (18)
Hi guys, there were some changes on mongo-memory-server so I paste here the updated class with version 7:
Hello there,
I write down everything as you posted but I still get warning in jest:
"A worker process has failed to exit gracefully and has been force exited. This is likely caused by tests leaking due to improper teardown. Try running with --detectOpenHandles to find leaks."
Do you know how to fix it?
Hi Pawel,
Sometimes I have this message. I tweaked a bit by test command with a few options.
--detectOpenHandles
should help you find the non-closing process. It might also remove the message--force-exit
will make sure to kill the process and allow your test to run smoothly on CI environments. It is a bit barbaric but it gets the job done.I've done a few tests before replying. The first screenshot does not use the
--detectOpenHandles
options and the seconds do."test:watch": "jest --watch",
"test:watch": "jest --watch --detectOpenHandles",
Hello, thank you for your answer but those flags unfortunately don't turn off those warnings but I just wanted to know if those warnings mean anything meaningful or should I just brush it off? Cheers!
Edit. I'm fairly new to Node/Nest stuff, could you please also tell me how can I add helper method to this utility to clear all collections? Cheers!
You don't need to clear collection between tests since it's a new in-memory mongo.
during the test I do something like that:
like the service in the test, the module needs to be accessible.
Could you maybe provide a full example of that? I tried to get this working in my own repo and in yours, using the
Squid
model, but got an error in both cases:Nest could not find Squid element (this provider does not exist in the current context)
. Much appreciated!You can check my Colonie repo. The pvp-shield module have the in memory db, collection deletion between
it
and the way to get the model from the testing module.github.com/bassochette/colonies/tr...
Thanks a lot, I managed to do it! For others who might struggle: I wasn't aware of the convention for the model name to use in
module.get()
. It's the name property that gets passed intoMongooseModule.forFeature()
+ the suffix 'Model' (SquidModel
in this example). Apologies if this obvious, but this string is never explicitly defined as a constant or class name etc.Oh, that's really not obvious...
I found this trick by mimicking the underlying metod of
InjectModel
from the nest/mongoose repo.github.com/nestjs/mongoose/blob/28...
Thank you for the article, this was very helpful. I also got the "Jest did not exit one second after the test run has completed. This usually means that there are asynchronous operations that weren't stopped in your tests. Consider running Jest with
--detectOpenHandles
to troubleshoot this issue."The advice was not practical for me because in VSCode I often run 1-2 tests only (using the extension: Jest Runner).
I share what worked for me. Go to the MongooseTestModule.ts (typescript please :)
add:
Thanks for you help ❤
I'll try to update the post and the sample code repo on github.
Good article! In my scenario, i have two services, each with its own schema, but both schemas have relationship with each other, do you have any suggestion about how can i setup this tests? I just want to use mongoose populate function to get the related data.
thnks!
Using an in-memory MongoDB will not change the behavior of mongoose. Therefore, if the link data are seeded in the database you will be able to use the populate method.
Now what you might struggle with is: How to seed?
I see to path for you:
1: use the services.
they need to be available as providers in your testingModule.
The caveat here is: you won't be able to test independently both services.
2: get the models from the testingModule
You'll be able to bypass any logic and just add data for your test.
The caveat here: you are bypassing all business logic...
here is a little example:
hoping this will help you start :)
thanks for your article it really help me
when jest doesn't finish the execution try to use this in afterAll
afterAll(async () => {
await closeInMongodConnection();
mongoose.disconnect();
mongoose.connection.close();
});
I'll try to update the post and sample code on github soon :)
Thanks for your help.
@deejoe79 offered a similar solution in the shutdown function helper.
Really helpful, you make my life easier :)
This was super helpful! Thank you 🎉🥳
Happy to help ;)