Test Controllers in NestJS + Mongo With Jest
How to plan and write solid unit tests with Jest for your NestJS controllers when using MongoDB and Mongoose

In this article, you will learn, by example, how to plan and write solid unit tests with Jest for your NestJS controllers when using MongoDB and Mongoose. Most of the testing techniques used here can be applied without NestJS, using other DBMS (Database Management System) or even using a testing library other than Jest! Those are being used here due to their relevance and because of how much work they simplify.
Before jumping into the code, lets see a quick overview of the main libraries used (install them with NPM).
NestJS
A Node.js framework for writing server-side applications with JavaScript or, better yet, TypeScript. NestJS simplifies the architectural part of the development by providing its own out-of-the-box application architectural patterns, aimed to provide scalability, decoupling, easy maintenance, and, of course, testability. The NestJS core weekly downloads at NPM have been increasing consistently, with more than 1.3 million of downloads in the last week.

Mongo
I used “mongo” to reffer to MongoDB + Mongoose + MongoDB Memory Server. MongoDB is a performant NoSQL DBMS that stores data as JSON-like formatted documents. Mongoose is a JavaScript object modeling library that makes interacting with MongoDB easier and safer. MongoDB Memory Server is a library that spins up an actual/real MongoDB server programmatically from within Node.js.
Jest
Jest is a complete JavaScript testing framework developed by Meta (previously Facebook) with focus on simplicity and performance (tests run in parallel, for example). The Jest weekly downloads at NPM has been increasing consistently, with more than 17.3 million of downloads in the last week.

Implementation Objective
What do we want to implement and test in this guide? We will implement and test two endpoints in a NestJS controller. A GET endpoint mapped to a method called getArticle
and a POST endpoint mapped to a method called postArticle
.
The code and project structure used here are simplified to focus on the testing aspects. If you want to see more tests and code in a more complete project context, refer to the following GitHub repository:
This was a challenge from Coodesh. Simply put, the purpose is to make an API capable of interfacing CRUD (Create, Read, Update and Delete) operations on a database of space flight news articles.
From Where To Start?
How would you start to write your code and the tests? If you already have your code done, good! You can write your tests. But I would like to quickly talk about a good practice know as Test Driven Development (TDD).
In TDD, we follow these three rules:
1- You should not write any production code before writing an unit test that ensures the correct behavior.
2- You should not write more tests than enough to the failure.
3- You should not write more production code than enough to pass the test.
These rules lead us to the following workflow:

First, we write the tests that ensure the correct behavior of the algorithm we want to implement (“with the X input, the exception, error, or result expected is Y”). Then, we write just enough code to pass the tests. Finally, when we improve our code, we go back to the first step and a cycle is created.
Directly answering the question in the topic, we will start from the tests.
Plan the Tests
The importance of this part cannot be overstated. If we plan our tests wrong, our code will also be wrong, thus making us waste time and effort.
The tests should reflect the requirements for the feature/functionality you are trying to implement. Another person must be able to know what your code takes as input, what it gives as output and when and how it will fail. The test is the best documentation possible to your code.
Our postArticle
method must receive an article as parameter and return the saved version of the article or an exception, in case another article with the same title is already saved. The getArticle
method must receive a title as parameter and return the article with the corresponding title or null
. These are the behaviors we are going to test.
Note: Here we are going to use the title of the article (string) as an identifier, but a far better idea is to use the own database id (usually a positive integer).
Prepare the Basics
The Controller, Module and Service
To start from the scratch, your controller, module, and service files should look like the following:
NestJS may already put some simple methods in your controller and service files when you create them using the Nest CLI, but remove them and their dependencies if that is the case.
The Article
We want to save articles to our database, but what exactly is an article? We must define the structure of an article.
Since we also want to persist (save) the articles in our MongoDB database, we must create the corresponding MongoDB schema.
The Stubs
When testing, we use “stubs”. Those are instances, with predefined values, of the objects that we are going to use when testing. It is a good practice to create a folder called “test” in the root of your project with a subfolder called “stubs” and save your stubs there.
Since we will need an instance of an article to test our code, lets make an article stub.
Notice that we are exporting an instance of the ArticleDTO
called ArticleSTOStub
with predefined values.
The Exception
We want to return an exception when someone tries to save an article with a repeated title, so lets write this exception.
We named it ArticleAlreadyExists
, defined a custom error message (“Article already exists!”) and set the HTTP response code to 400 (Bad Request).
The Database Connection
To setup the connection with the database, we could do something like this in our controller:
Basically, we used the MongooseModule
to pass our schema with the forFeature
method and the MongoDB connection URI with the forRoot
method. Note that here we are passing our connection URI through an environment variable to avoid exposing it as a string in our code.
Write The Tests
The Base for a Test File
First, lets create our test file. If NestJS already created one for you, delete it and follow along with us. In case of unit tests, create the test file in the same folder as the file with the code to be tested and name it by appending the .spec suffix in the name of the file with the code to be tested. Jest also expects those conventions by default. So, if the controller is called app.controller.ts
, the test file will be called app.controller.spec.ts
and should be in the same folder.
With the file created, lets start writing some code.
Line 1: We imported a class called
Test
and a type calledTestingModule
from@nestjs/testing
.Line 2: We imported the controller we want to test.
Line 3: We imported the service that our controller uses/depends.
Line 6: The
describe
from Jest creates a block of tests and lets you pass a string as parameter in order to indicate their general purpose. Since we want to test ourAppController
, lets just write it.Line 7: Soon, we will instantiate our controller in order to access its methods (
postArticle
andgetArticle
), but lets declare it here in the global scope of this block so that we can refer to our controller inside any test (since each test will be a function inside the block with its own scope).Line 9: The
beforeAll
from Jest creates a block of code that will be run before any of the tests.Line 10–15: We compile our application (
app
) passing the controller we want to test and the providers it needs.Line 16: From our compiled
app
, we get the instance of the controller we need (appController
).
If you write jest
in your terminal and press enter to run, you will see an error, but if you read carefully (always pay attention to the error messages), you will find what the problem is: Your test suite must contain at least one test.
We did lay some foundations, but we still have not written any test.
The Database Connection Problem
We do not want to mess with our production database when testing. You can think about setting up a remote test database, but that also has several problems such as schema structures mismatches, the inability to test offline and the lack of control over the state of the database when multiple people are using it.
The most used approach is “mocking” the interactions with the database, that is, setting predefined responses to the methods involved in the retrieval or alteration of data without really relying in any kind of database instance.
This method, however, usually becomes confusing quickly and adds one more layer of artificiality in your tests. A better approach is to run an on-demand and dedicated instance of the database in you RAM for testing. That is what we are going to do with MongoDBMemoryServer
.
Line 17: We create a new MongoDB server and get the daemon (
mongod
)Line 18: We get the connection URI for the MongoDB server we just started
Line 19: We connect and get the connection to the MongoDB server using the URI
Line 20: Through the connection, we get the model of the schema we want to manipulate (
Article
)Line 25: We provide our
Article
schema model, akin to what we did previously in theapp.module.ts
. The difference here is just that this model is from our temporary RAM database, and not from our real database specified by theMONGODB_CONNECTION_URI
environment variable.
Now, lets add two more blocks from Jest, the afterAll
and afterEach
.
Line 31–35: Here, we are saying that after all our tests have run, we have to drop (delete) the database, close the connection and stop the daemon.
Line 37–43: Now, we are saying that after each individual test, we are going to delete all entries (documents/data) from our collections. In this case, we only have one collection (for the articles), but it is a good practice to make it more generalist. The idea is to always start a test with a clean database.
Testing postArticle
Now, we are finally able to write a batch of tests for the postArticle
method. Just to remember, this method must receive an article as parameter and return the saved version of the article or an exception, in case another article with the same title is already saved.
To create a batch of tests, we use a describe
block from Jest with one or more it
blocks. An it
block corresponds to one specific test scenario and should call the expect
function at its end to check the condition that validates the test.
Based on our requirements, we can think in 2 simple test scenarios.
1 — The article is valid and should be saved without any problem.
2 — The article title is repeated and an exception should be thrown.
We will write one describe
block for the postArticle
method with two it
blocks, one for each one of the tests.
Line 49: We pass our article stub to the
postArticle
method and get the returned created article.Line 50: We use the
expect
function to check if the title of the created article is equal to the title of the article we passed to be saved. ThetoBe
method specifies the logical comparison we want to perform. There are a lot of different methods for a variety of checks. If this expression returns anything other thantrue
, the test is considered failed.Line 53: Here, we save our article stub in the database using mongoose (notice that we are using the
articleModel
). We could use ourpostArticle
method to save the article stub, but the method might contain more logic than a simple save, thus introducing more uncertainties when we simply want to put something in the database.Line 54–56: Here, we use the
postArticle
method and try to save an article that already exists in the database, since we saved it in the line above, and check to see if the expected exception will be thrown. Therejects
method is used when you want to deal with rejected promises and thetoThrow
method checks if the error is an instance ofArticleAlreadyExists
.
Remember that postArticle
still does not exist, so your IDE might warn you about that and if you try to run the test file, you will also get errors related to that. However, realize that now we already know how the method must behave and we have an easy way to check that and show to others.
Testing getArticle
The getArticle
method must receive a title as parameter and return the article with the corresponding title or return null
.
Based on our requirements, we can think in 2 simple test scenarios.
1 — The article with the corresponding title is found and returned.
2 — There is no article with the corresponding title and null
should be returned.
Let's write the tests.
Line 62: Again, it is important to not use the
postArticle
method here to save the article. If you used it here and an error occurred in thepostArticle
method, the test would say that an error happened when testinggetArticle
, making things more confusing. Always try to isolate what you are testing.Line 63: Here we use
getArticle
to search for the title of the article we previously saved and store the return.Line 64: Now, we check if the title of the article found and returned is equal to the one of the article we saved.
Line 67: Without previously saving anything to the database (remember that the
afterEach
method clears the database after each test), we try to find an article by its title.Line 68: We use
toBeNull
to check if the object article isnull
, as expected.
Implement the Methods
Now that the tests are done, we should write just enough code to pass them, since passing the tests means that the functionalities are complete and accurate.
The focus of the article was the tests, so there will not be much explanation about the implementation of the methods itself.
Run the Tests
Now, when you execute npm test
in the terminal, all tests should run and pass without problems.

To see the test coverage, use npm run test:cov
. The test coverage result shows how much of your code is tested by all your tests and is a good metric for code quality.

In order to make your tests run automatically whenever you make changes to your code, use npm run test:watch
. There are also some useful VS Code extensions to help you tracking your tests execution, such as the “Jest” extension from “Orta”.
Final Considerations
This was by no means an exhaustive review of Jest or TDD, but I really hope you could find something useful here. After reading the article, I recommend taking a look at the Jest Documentation for more information.
Thanks for reading.