Logo

dev-resources.site

for different kinds of informations.

Running Unit Tests with MongoDB in a Node.js Express Application using Jest

Published at
5/26/2024
Categories
testing
express
mongodb
mongoose
Author
ndohjapan
Categories
4 categories in total
testing
open
express
open
mongodb
open
mongoose
open
Author
9 person written this
ndohjapan
open
Running Unit Tests with MongoDB in a Node.js Express Application using Jest

Hey everyone!

It's been a while since I last posted, and I'm excited to share something new today. Let's dive into running unit tests with MongoDB in a Node.js Express application. If you've ever struggled to set this up, this post is for you!

Why This Post?

Online resources often cover automated test setups for Node.js with SQL databases like PostgreSQL. However, setting up unit tests for Node.js, Express, and MongoDB is less common and can be quite challenging. Issues with running MongoDB in-memory for each test and the speed of test execution are just a couple of the hurdles.

Having used MongoDB for over three years, I've developed a robust method for running tests with MongoDB. My goal here is to help you set up your Node.js Express app to run tests smoothly with MongoDB.

Installing the Necessary Packages

Let's start by installing the essential packages:

  1. jest: Jest is our test runner for Node.js Express applications.
  2. jest-watch-typeahead: This package makes it easier to run specific tests. It allows us to select and search for test files and rerun failed tests quickly.
  3. supertest: Supertest allows us to simulate API requests in our tests. mongodb The MongoDB driver for JavaScript.
  4. uuid: This package generates unique IDs for each database used in our tests.

Setting Up the Jest Config File

Here's where we configure Jest to work with our setup. We include jest-watch-typeahead for flexibility and specify the test folder.

jest.config.js

module.exports = {
  testRegex: './__tests__/.*\\.spec\\.js$',
  watchPlugins: [
    'jest-watch-typeahead/filename',
    'jest-watch-typeahead/testname',
  ],
  testPathIgnorePatterns: ['<rootDir>/node_modules/', '<rootDir>/config/'],
  testEnvironment: 'node',
};
Enter fullscreen mode Exit fullscreen mode

Handling Database Setup and Cleanup

Understanding the problem: By default, running tests in SQL databases can be done in-memory, which is fast and isolated. However, MongoDB lacks built-in in-memory support for isolated tests. Packages like mongodb-memory-server exist but have limitations with isolation.

Limitations of mongodb-memory-server

The mongodb-memory-server package allows us to spin up an in-memory instance of MongoDB for testing purposes. However, it comes with several limitations:

  1. Shared Instance: mongodb-memory-server does not create separate instances for each test case. All tests share the same in-memory database, which can lead to data collisions and inconsistent test results.

  2. Performance Overhead: Running an in-memory MongoDB instance can be resource-intensive. For large test suites, this can slow down the overall execution time and lead to performance bottlenecks.

  3. Limited Features: Some features and configurations available in a full MongoDB instance may not be supported or fully functional in the in-memory server. This can lead to discrepancies between test and production environments.

  4. Scalability Issues: As the number of tests grows, managing the in-memory database can become increasingly complex. Ensuring data isolation and cleanup becomes a significant challenge.
    Given these limitations, we need a solution that ensures each test runs in an isolated environment, mimicking the behavior of in-memory databases used with SQL.

Our Solution

We'll create a new database for each test and drop it afterward. This approach uses UUIDs to ensure uniqueness and avoids database conflicts.

Setup File: setup.js

const mongoose = require('mongoose');
const { connectToDatabase } = require('../connection/db-conn');
const { redis_client } = require('../connection/redis-conn');
const { v4: uuidv4 } = require('uuid');

const setup = () => {
  beforeEach(async () => {
    await connectToDatabase(`mongodb://127.0.0.1:27017/test_${uuidv4()}`);
  });

  afterEach(async () => {
    await mongoose.connection.dropDatabase();
    await mongoose.connection.close();
    await redis_client.flushDb();
  });
};

module.exports = { setup }; 
Enter fullscreen mode Exit fullscreen mode

Database Cleanup Script

To handle databases that may not be dropped properly after tests, we create a cleanup script. This script deletes all test databases whose names start with "test_".

Database Cleanup File: testdb-cleanup.js

const { MongoClient } = require('mongodb');

const deleteTestDatabases = async () => {
  const url = 'mongodb://127.0.0.1:27017';
  const client = new MongoClient(url);

  try {
    await client.connect();
    const databaseNames = await client.db().admin().listDatabases();
    const testDatabaseNames = databaseNames.databases.filter(db => db.name.startsWith('test_'));

    for (const database of testDatabaseNames) {
      await client.db(database.name).dropDatabase();
      console.log(`Deleted database: ${database.name}`);
    }
  } catch (error) {
    console.error('Error deleting test databases:', error);
  } finally {
    await client.close();
  }
};

deleteTestDatabases(); 
Updating package.json to Include Database Cleanup
To ensure cleanup happens after tests run, we update package.json:
package.json
{
  "scripts": {
    "start": "node index.js",
    "dev": "nodemon index.js",
    "test": "jest --runInBand --watch ./__tests__ && npm run test:cleanup",
    "test:cleanup": "node ./__tests__/testdb-cleanup.js"
  }
}

Enter fullscreen mode Exit fullscreen mode

Running Tests

We'll place our tests in a folder called tests. Inside, we'll have apis for API tests and functions for unit tests.

Writing Tests

Use describe to group similar tests.

Function Tests: ./__tests__/functions/Rizz.spec.js

const RizzService = require('../../controllers/rizz-controller');
const Rizz = require('../../database/model/Rizz');
const { setup } = require('../setup');
setup();

describe('Getting the rizz', () => {
  it('should return the rizzes added to the database', async () => {
    await Rizz.create([{ text: 'First Rizz' }, { text: 'Second Rizz' }]);
    const result = await RizzService.GetLatestRizz();
    expect(result.total_docs).toBe(2);
  });
});

describe('Liking a rizz', () => {
  it('should increase the likes count of a rizz', async () => {
    const rizz = await Rizz.create({ text: 'First Rizz' });
    await RizzService.LikeRizz(rizz._id);
    const updatedRizz = await Rizz.findById(rizz._id);
    expect(updatedRizz.likes).toBe(1);
  });
}); 

Enter fullscreen mode Exit fullscreen mode

API Tests: ./__tests__/apis/Rizz.spec.js

const request = require('supertest');
const { app } = require('../../app');
const { setup } = require('../setup');
setup();

describe('Rizz API', () => {
  it('should return the latest rizzes', async () => {
    await request(app)
      .post('/api/v1/rizz')
      .send({ text: 'First Rizz' });

    const response = await request(app)
      .get('/api/v1/rizz/latest?page=1&limit=100')
      .send();

    expect(response.body.data.total_docs).toBe(1);
  });

  it('should like a rizz', async () => {
    const rizzResponse = await request(app)
      .post('/api/v1/rizz')
      .send({ text: 'First Rizz' });

    const rizzId = rizzResponse.body.data._id;

    const response = await request(app)
      .post(`/api/v1/rizz/${rizzId}/like`)
      .send();

    expect(response.body.data.likes).toBe(1);
  });
}); 

Enter fullscreen mode Exit fullscreen mode

Conclusion

Setting up unit tests with MongoDB in a Node.js Express app doesn't have to be daunting. By following these steps, you can ensure isolated, efficient tests. For the complete code, check out my GitHub repository:

GitHub - https://github.com/Ndohjapan/get-your-rizz

Happy testing! ๐Ÿš€ #NodeJS #Express #MongoDB #AutomatedTesting #SoftwareDevelopment

mongoose Article's
30 articles in total
Favicon
Crudify: Automate Your Mongoose CRUD Operations in NestJS
Favicon
6 Steps to Set Up MongoDB Atlas for Node.js Applications
Favicon
Mysql 101 for Mongoose developer.
Favicon
Tutorial de Instalaรงรฃo: Express com MongoDB e Mongoose
Favicon
Todayโ€™s new knowledge #6(Mongoose)
Favicon
Todayโ€™s new knowledge #10 (Building a Flexible Query Builder for MongoDB with Mongoose)
Favicon
mongoose connect to express
Favicon
I Fumbled on a Next.js MongoDB Error and Learned the Key Differences Between Mongoose and MongoClient
Favicon
Setup Eslint Prettier in a TypeScript project with mongoose ODM
Favicon
Bootcamping 01: An Unexpected Behavior of Mongoose
Favicon
Common Myths About Mongoose
Favicon
5 Quick And Easy MongoDB Optimizations (part 1)
Favicon
Mongoose Interview Questions
Favicon
MongoDB vs. Mongoose: Understanding Their Roles and Differences
Favicon
We finally have a fullstack framework for MongoDB
Favicon
Mongoose
Favicon
๐Ÿ’ฌ Building a Real-time Chat Feature for Virtual Gift Store Using Socket.IO with MERN Stack ๐Ÿš€
Favicon
The Power of exec() in Mongoose: Unlocking Better Query Execution
Favicon
Enhancing Mongoose Reference Handling in Node.js
Favicon
Mongoose Documentation
Favicon
How to Connect MongoDB with Node.js: A Comprehensive Guide
Favicon
Updating Non-Primitive Data in an Array Using Transactions and Rollbacks
Favicon
Method Chaining in Mongoose: A Brief Overview
Favicon
Understanding Transactions and Rollbacks in MongoDB
Favicon
Understanding Populating Referencing Fields in Mongoose
Favicon
How to Use Bcrypt for Password Hashing in Node.js
Favicon
Getting Started with Mongoose
Favicon
Running Unit Tests with MongoDB in a Node.js Express Application using Jest
Favicon
Setting up MongoDB using Mongoose in Node.js
Favicon
I built an open-source schema visualisation tool for mongoose

Featured ones: