Search code examples
javascriptnode.jsmongodbmocha.jssinon

How do i mock MongoDb Connection and Test it


My DB Class to test I have a DB class where i encapsulate Mongodb methods to test

import mongodb from 'mongodb';
import dotenv from 'dotenv';

// Comfigure dotenv
dotenv.config();

// eslint-disable-next-line no-unused-vars
class DBClient {
  /** 
  * Creates a new DBclient instance.
  */
  constructor() {
    const host = process.env.DB_HOST || 'localhost';
    const port = process.env.DB_PORT || 27017;
    const database = process.env.DB_DATABASE || 'files_manager';

    const dbUri = `mongodb://${host}:${port}/${database}`;
    this.client = new mongodb.MongoClient(dbUri, {
      useNewUrlParser: true,
      useUnifiedTopology: true,
    }); 

    this.isClientConnected = false;
    this.client.connect((err) => {
      if (err) {
        console.error('Error encounted while connecting to MongoDB', err);
      } else {
        this.isClientConnected = true;
        console.log('Connected to MongoDB');
      }   
    }); 
  }

  /** 
  * check the status of the connection to MongoDB
  * @returns {boolean}
  */
  isAlive() {
    return this.isClientConnected;
  }
}

const dbClient = new DBClient();
module.exports = dbClient;

My Test File My intention is to mock the connection to db then test

const { expect } = require('chai');
const sinon = require('sinon');
const mongodb = require('mongodb');
const dbClient = require('../utils/db');

describe('DBClient', function() {
  afterEach(function() {
    // Automatically restore all stubs after each test
    sinon.restore();
  }); 

  it('should initialize and connect successfully', function(done) {
    const connectStub = sinon.stub(mongodb.MongoClient.prototype, 'connect').callsFake(function(cb) {
      cb(null); // No Error
    });  
    // Wait for the next tick to allow the callback to execute
    process.nextTick(() => {
      expect(dbClient.isAlive()).to.be.true;
      expect(connectStub.calledOnce).to.be.true;
      done();
    }); 
  }); 
  
  it('should fail to connect and isAlive returns false', function(done) {
    // Stub the connect method to simulate a connection failure
    const connectStub = sinon.stub(mongodb.MongoClient.prototype, 'connect').callsFake(function(cb) {
      cb(new Error('Failed to connect')); 
    }); 

    process.nextTick(() => {
      expect(dbClient.isAlive()).to.be.false;
      expect(connectStub.calledOnce).to.be.true();
      done();
    }); 
  }); 
});

When i run the test The two test cases are falling with the following error.

  DBClient
    1) should initialize and connect successfully
    2) should fail to connect and isAlive returns false


  0 passing (20ms)
  2 failing

  1) DBClient
       should initialize and connect successfully:

      Uncaught AssertionError: expected false to be true
      + expected - actual

      -false
      +true
      
      at /root/alx-files_manager/test/connectToMongoDb.test.js:18:39
      at processTicksAndRejections (node:internal/process/task_queues:77:11)

  2) DBClient
       should fail to connect and isAlive returns false:

      Uncaught AssertionError: expected false to be true
      + expected - actual

      -false
      +true
      
      at /root/alx-files_manager/test/connectToMongoDb.test.js:32:43
      at processTicksAndRejections (node:internal/process/task_queues:77:11)

I suspect my issue is starting when I stub the connection to the DB. Please assist


Solution

  • The instantiation of the DBClient class is executed immediately when the module is referenced. You need to require the ./utils/db module after stubbing client.connect() method.

    Besides, don't forget to clear the module cache from require.cache in afterEach hook.

    e.g.

    db.js:

    const mongodb = require('mongodb');
    
    class DBClient {
        constructor() {
            const host = process.env.DB_HOST || 'localhost';
            const port = process.env.DB_PORT || 27017;
            const database = process.env.DB_DATABASE || 'files_manager';
    
            const dbUri = `mongodb://${host}:${port}/${database}`;
            this.client = new mongodb.MongoClient(dbUri, {
                useNewUrlParser: true,
                useUnifiedTopology: true,
            });
    
            this.isClientConnected = false;
            this.client.connect((err) => {
                if (err) {
                    console.error('Error encounted while connecting to MongoDB', err);
                } else {
                    this.isClientConnected = true;
                    console.log('Connected to MongoDB');
                }
            });
        }
    
        isAlive() {
            return this.isClientConnected;
        }
    }
    
    const dbClient = new DBClient();
    
    module.exports = dbClient;
    

    db.test.js:

    const { expect } = require('chai');
    const sinon = require('sinon');
    const mongodb = require('mongodb');
    
    describe('DBClient', function () {
        afterEach(function () {
            sinon.restore();
            delete require.cache[require.resolve('./db')];
        });
    
        it('should initialize and connect successfully', function (done) {
            const connectStub = sinon.stub(mongodb.MongoClient.prototype, 'connect').callsFake(function (cb) {
                cb(null); // No Error
            });
    
            const dbClient = require('./db');
            process.nextTick(() => {
                expect(dbClient.isAlive()).to.be.true;
                expect(connectStub.calledOnce).to.be.true;
                done();
            });
        });
    
        it('should fail to connect and isAlive returns false', function (done) {
            const connectStub = sinon.stub(mongodb.MongoClient.prototype, 'connect').callsFake(function (cb) {
                cb(new Error('Failed to connect'));
            });
            const dbClient = require('./db');
    
            process.nextTick(() => {
                expect(dbClient.isAlive()).to.be.false;
                expect(connectStub.calledOnce).to.be.true;
                done();
            });
        });
    });
    

    Test result:

      DBClient
    Connected to MongoDB
        √ should initialize and connect successfully (42ms)
    Error encounted while connecting to MongoDB Error: Failed to connect
        at MongoClient.<anonymous> (D:\workspace\mrdulin\expressjs-research\src\stackoverflow\78980387\db.test.js:26:7)
        at Object.invoke (D:\workspace\mrdulin\expressjs-research\node_modules\sinon\lib\sinon\behavior.js:163:32)
        at MongoClient.functionStub (D:\workspace\mrdulin\expressjs-research\node_modules\sinon\lib\sinon\stub.js:39:43)
        at Function.invoke (D:\workspace\mrdulin\expressjs-research\node_modules\sinon\lib\sinon\proxy-invoke.js:47:47)
        at MongoClient.connect (D:\workspace\mrdulin\expressjs-research\node_modules\sinon\lib\sinon\proxy.js:219:26)
        at new DBClient (D:\workspace\mrdulin\expressjs-research\src\stackoverflow\78980387\db.js:16:15)
        at Object.<anonymous> (D:\workspace\mrdulin\expressjs-research\src\stackoverflow\78980387\db.js:31:18)
        at Module._compile (node:internal/modules/cjs/loader:1198:14)
        at Module.m._compile (D:\workspace\mrdulin\expressjs-research\node_modules\ts-node\src\index.ts:1618:23)
        at Module._extensions..js (node:internal/modules/cjs/loader:1252:10)
        at Object.require.extensions.<computed> [as .js] (D:\workspace\mrdulin\expressjs-research\node_modules\ts-node\src\index.ts:1621:12)
        at Module.load (node:internal/modules/cjs/loader:1076:32)
        at Function.Module._load (node:internal/modules/cjs/loader:911:12)
        at Module.require (node:internal/modules/cjs/loader:1100:19)
        at require (node:internal/modules/cjs/helpers:108:18)
        at Context.<anonymous> (D:\workspace\mrdulin\expressjs-research\src\stackoverflow\78980387\db.test.js:28:20)
        at callFnAsync (D:\workspace\mrdulin\expressjs-research\node_modules\mocha\lib\runnable.js:392:21)
        at Test.Runnable.run (D:\workspace\mrdulin\expressjs-research\node_modules\mocha\lib\runnable.js:336:7)
        at Runner.runTest (D:\workspace\mrdulin\expressjs-research\node_modules\mocha\lib\runner.js:677:10)
        at D:\workspace\mrdulin\expressjs-research\node_modules\mocha\lib\runner.js:801:12
        at next (D:\workspace\mrdulin\expressjs-research\node_modules\mocha\lib\runner.js:594:14)
        at D:\workspace\mrdulin\expressjs-research\node_modules\mocha\lib\runner.js:604:7
        at next (D:\workspace\mrdulin\expressjs-research\node_modules\mocha\lib\runner.js:486:14)
        at Immediate._onImmediate (D:\workspace\mrdulin\expressjs-research\node_modules\mocha\lib\runner.js:572:5)
        at processImmediate (node:internal/timers:466:21)
        √ should fail to connect and isAlive returns false
    
    
      2 passing (73ms)