Search code examples
typescriptunit-testingjestjsmockingts-jest

How to mock mongodb manager in typescript using jest


How can I return a mongodb.connection.db() type value and mock its collection? I create a mongoClient type connection and use its db() function. If all works work correctly, I enter the collection information of this but i couldn't write test of it because i can't mock it.

dbManager.ts

import { MongoClient } from 'mongodb';

class DBManager {
  private connection?: MongoClient;
  private mongoUrl = null;

  constructor(url: string) {
    this.mongoUrl = url;
  }

  get db() {
    return this.connection!.db();
  }

  async start() {
    if (!this.connection) {
      this.connection = await MongoClient.connect(this.mongoUrl);
    }
  }
}

export default DBManager;

index.ts

 const dbManager = new DBManager(url);
  await dbManager.start();
  const db = dbManager.db;

  if (db) {
    const collection = db.collection(collectionName);
  }

index.spec.ts

  const dbManager = new DBManager( 'mongoUrl');
      jest.spyOn(dbManager, 'start').mockResolvedValue();
      jest.spyOn(dbManager, 'db', 'get').mockImplementation();

Solution

  • The reason why the mock is not successful is that you created a new DBManager instance in the test case, and jest.spyOn() only adds spy to the method of this instance. In the tested code is another instance of DBManager, still calling its original, unspy method.

    You can add spy to DBManager.prototype.start() and DBManager.prototype.get() methods.

    Besides, don't forget to restore mocks back to their original value in afterEach hook. To ensure that the mock object of the test case will not affect other test cases.

    E.g.

    DBManager.ts:

    import { MongoClient } from 'mongodb';
    
    class DBManager {
      private connection?: MongoClient;
      private mongoUrl: string = '';
    
      constructor(url: string) {
        this.mongoUrl = url;
      }
    
      get db() {
        return this.connection!.db();
      }
    
      async start() {
        if (!this.connection) {
          this.connection = await MongoClient.connect(this.mongoUrl);
        }
      }
    }
    
    export default DBManager;
    

    index.ts:

    import DBManager from './dbManager';
    
    export async function main() {
      const url = 'mongodb://localhost:27017';
      const collectionName = 'user';
      const dbManager = new DBManager(url);
      await dbManager.start();
      const db = dbManager.db;
    
      if (db) {
        const collection = db.collection(collectionName);
      }
    }
    

    index.test.ts:

    import { main } from './';
    import DBManager from './dbManager';
    import { Db } from 'mongodb';
    
    describe('69011729', () => {
      afterEach(() => {
        jest.restoreAllMocks();
      });
      test('should pass', async () => {
        const mDB = ({
          collection: jest.fn(),
        } as unknown) as Db;
        jest.spyOn(DBManager.prototype, 'start').mockResolvedValue();
        jest.spyOn(DBManager.prototype, 'db', 'get').mockReturnValueOnce(mDB);
        await main();
        expect(DBManager.prototype.start).toBeCalledTimes(1);
        expect(mDB.collection).toBeCalledWith('user');
      });
    
      test('should restore original methods', () => {
        expect(jest.isMockFunction(DBManager.prototype.start)).toBeFalsy();
      });
    });
    

    test result:

     PASS  examples/69011729/index.test.ts (10.734 s)
      69011729
        ✓ should pass (4 ms)
        ✓ should restore original methods
    
    Test Suites: 1 passed, 1 total
    Tests:       2 passed, 2 total
    Snapshots:   0 total
    Time:        11.516 s
    Ran all test suites related to changed files.