Friday, March 12, 2021

How to test a NodeJS module and swap out its private dependent class with a fake

 TL;DR

  • Use rewiremock
  • Create a FakeDep class with which to replace the real one
  • rewiremock('../../../real/code/my-real-class').with(FakeDep);
  • rewiremock.enable();

Details

When you're unit-testing a module, you want to isolate your testing to only that one piece.  You presumably have other unit tests for the dependencies you have written.

All day today I have been trying to swap out a private class dependency inside of a SUT.  The SUT creates a new instance of the class returned.

I found examples on how to swap out dependencies this way and that--but never a complete example on how to swap out a module that returns a class that is later newed up by the SUT.  I know there are others out there looking for this solution because I saw their questions that have gone unanswered--at least unanswered completely.

I tried using proxyquire, but I just kept spinning my wheels.  The SUT kept newing up the actual dependency and not my fake.  At the end of it, I submitted an issue asking them to add an example for my use case and moved on.

Anyway, here's what I ended up with.  
Please excuse the nature of the code, as it has been sanitized.

// ./real/code/my-sut.js
const MyRealPrivateDependency = require('./my-real-class'); // Not shown
const dep = new MyRealPrivateDependency('param');
// We want dep to be replaced with FakeDep

async function doSomethingCool (filters) {
await dep.doSomethingCool(filters, true);
}

exports.doSomethingCool = doSomethingCool;

// ./test/A/sut-tests.js
const sinon = require('sinon');
const chai = require('chai');
const sinonChai = require('sinon-chai');
chai.use(sinonChai);
const expect = chai.expect;

describe('A', () => {
describe('B', () => {
describe('SUT', () => {
it('should doSomethingCool', async() => {
// Arrange
const rewiremock = require('rewiremock/node');
const stub = sinon.stub().resolves();
class FakeDep {
async iterate() {
return await stub(...arguments);
}
}

rewiremock('../../../real/code/my-real-class').with(FakeDep);
rewiremock.enable();
const sut = require('../../../real/code/my-sut');
const filters = {};
// Act
await sut.doSomethingCool(filters);
// Assert
expect(stub).to.have.been.calledOnce;
expect(stub).to.have.been.calledWith(filters, true);
});
});
});
});


Alternatively, instead of creating a new fake class, you can require the dependency, swap out the prototype method you want to change with the stub, and call it a day like this:

const FakeDep = require('../../../lambdas/maintenance/my-real-class');
FakeDep.prototype.doSomethingCool = stub;
rewiremock('../../../real/code/my-real-class').with(FakeDep);

Enjoy!