Search code examples
typescriptunit-testingmockingmocha.jssinon

How to mock entire class/module-level dependencies in the mocha ecosystem like jest.mock?


I'm trying to unit test a module that looks something like this

import { Countdown } from "./database/orm";

export class PersistentTimer {
    protected constructor(...) { ... }
    
    // This object requires writing to a database, so I protect the constructor and
    // force construction through an async interface
    public static async create(...) {
        const entity = new Countdown();
        // do stuff
        await entity.save();
        return new PersistentTimer(entity);
    }
}

I'm looking for something like jest.mock within the mocha ecosystem to mock the Countdown module since it has a side effect so that I can unit test this module. Sinon seems to only deal with functions, properties on objects, or object instances passed to function calls.


Solution

  • You can stub out ./database/orm module with link seams.

    This is the CommonJS version, so we will be using proxyquire to construct our seams.

    E.g.

    main.ts:

    import { Countdown } from './database/orm';
    
    export class PersistentTimer {
      protected constructor(entity: Countdown) {}
    
      public static async create() {
        const entity = new Countdown();
        await entity.save();
        return new PersistentTimer(entity);
      }
    }
    

    main.test.ts:

    import sinon from 'sinon';
    import proxyquire from 'proxyquire';
    import { expect } from 'chai';
    
    describe('65399764', () => {
      it('should pass', async () => {
        const countdownInstanceStub = {
          save: sinon.stub(),
        };
        const CountdownStub = sinon.stub().returns(countdownInstanceStub);
        const { PersistentTimer } = proxyquire('./main.ts', {
          './database/orm': {
            Countdown: CountdownStub,
          },
        });
        const persistentTimer = await PersistentTimer.create();
        sinon.assert.calledOnce(CountdownStub);
        sinon.assert.calledOnce(countdownInstanceStub.save);
        expect(persistentTimer).to.be.instanceOf(PersistentTimer);
      });
    });
    

    unit test result:

      65399764
        ✓ should pass (1848ms)
    
    
      1 passing (2s)
    
    -------------------|---------|----------|---------|---------|-------------------
    File               | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
    -------------------|---------|----------|---------|---------|-------------------
    All files          |   85.71 |      100 |   66.67 |   85.71 |                   
     65399764          |     100 |      100 |     100 |     100 |                   
      main.ts          |     100 |      100 |     100 |     100 |                   
     65399764/database |      50 |      100 |       0 |      50 |                   
      orm.ts           |      50 |      100 |       0 |      50 | 3                 
    -------------------|---------|----------|---------|---------|-------------------