rewire icon indicating copy to clipboard operation
rewire copied to clipboard

Inject Mocks at rewire time

Open richardpj opened this issue 8 years ago • 4 comments

I am trying to test a node shell script (in a sense a self executing module). It would be great if mocks could be injected at/prior to rewire time for this purpose.

richardpj avatar Oct 01 '16 14:10 richardpj

can you provide a code sample?

drjasonharrison avatar Feb 21 '17 18:02 drjasonharrison

I can.

'use strict';

const chai = require('chai');
const chaiAsPromised = require('chai-as-promised');
const lodash = require('lodash');
const sinon = require('sinon');
const sinonChai = require('sinon-chai');

describe('DataControl', function() {
    chai.use(chaiAsPromised);
    chai.use(sinonChai);
    chai.should();

    // Tested module
    let dataControl;

    // `dataControl`'s dependencies
    let common;
    let database;
    let logger;

    beforeEach(function() {
        // Mocked dependencies   // In `data-control.js`, they're declared like this:
        common = sinon.stub();   // const common = require('common');
        database = sinon.stub(); // const database = new common.DB();
        logger = sinon.stub();   // const logger = new common.Logger();

        // Tested module and its dependencies
        dataControl = rewire('../../../../app/business/data-control', {
            common,
            database,
            logger,
        });
    });

    describe('someTestedFunction(foo, bar)', function() {
        it('should do some stuff', function(done) {
            // Teste function arguments
            const foo = lodash.cloneDeep(fixtureFooGood);
            const bar = lodash.cloneDeep(fixtureBarBad);

            // Tested function
            const result = someTestedFunction(foo, bar);

            // Assertions
            result.should.be.fulfilled.then(() => {
                database.readStuff.should.have.been.calledWithExactly(foo);
                logger.info.should.have.been.calledWithExactly(bar);
            }).should.notify(done);
        });
    });
});

The idea here is that if you just common = rewire('common') and then i.e. common.__set__('DB', sinon.stub()), then the common module had an opportunity to run undesired code before we apply the stub.

Same thing with the database = new common.DB() call. We would like to prevent these lines of code to even be run, since they're not part of our unit test.

Passing the stub definitions like this to rewire would allow it to avoid running code that initialises a variable for which it already has a stub for. This is relevant for modules that run a lot of code directly when required. newrelic comes to mind (adds instrumentation to DBs), but is not the only case.

brocoli avatar May 21 '18 23:05 brocoli

At the very least we could have something like this for requires, like proxyquire does, but with rewire's injected setters / getters too.

brocoli avatar May 21 '18 23:05 brocoli

I know your reply is kind of outdated, but I agree with this; rewire should allow injection before execution; e.g. rewire("path/to/my/script", {...keyValuePairsToOverride}).

I think the simplest possible workaround is this is:

Suppose you have one file which is our "main" in package.json:

// index.js
const toBeOverridden = require("stuff")
// do stuff

Change it to two files:

// auxiliary.js
const toBeOverridden = require("stuff")
module.exports = () => {
  //do stuff
}

and

// index.js, our new "main" in package.json
const script = require("./auxiliary.js)
script()

Then, you can just unit test auxiliary.js

test-as-other-user avatar Jun 04 '18 21:06 test-as-other-user