jhnns / rewire

Easy monkey-patching for node.js unit tests
MIT License
3.08k stars 128 forks source link

Inject Mocks at rewire time #100

Open richardpj opened 8 years ago

richardpj commented 8 years ago

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.

drjasonharrison commented 7 years ago

can you provide a code sample?

brocoli commented 6 years ago

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 commented 6 years ago

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

test-as-other-user commented 6 years ago

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