hyprstack
hyprstack

Reputation: 4228

Sinon.stub not stubbing private method

In the following module I have a private method which creates a temporary directory. I am writing the tests for this module, resize, and want to stub its private method createTmpFile and return a fake directory. I am using sinon to do this, but the original method is still being called.

I've tried requiring the private method with var _private = require('../lib/modules/resizer')._private; and then stubbing it in my test suit with tmpStub = sinon.stub(_private, 'createTmpFile', function () { return "/temporary/"});

This throws no errors, but still stop the function from being called.

How can I replace the private method with the stub?

resize

'use strict';

// dependencies
var gm = require('gm').subClass({ imageMagick: true });
var tmp = require('tmp');
var async = require('async');

var resizer = {};

resizer.resize = function (path, sizesObj, callback) {
    console.log(path);

    var directory = createTmpFile();

    var imgType = path.split(".").pop();

    async.each(sizesObj, function (sizesObj, mapNext) {
        gm(path)
            .resize(sizesObj.width, sizesObj.height)
            .write(directory + sizesObj.name + "." + imgType, function (err) {
                if (err) {
                    mapNext(err);
                }
                mapNext();
            });
    }, function (err) {
        if(err) {
            callback(err);
        } else {
            callback(directory);
        }
    });
};

// This function creates a temporary directory to which we will save our files.
var createTmpFile = function () {

    var tmpDir = tmp.dirSync(); //object
    var tmpDirName = tmpDir.name + "/"; //path to directory
    return tmpDirName;
};

module.exports = resizer;

if ( process.env.NODE_ENV === 'test') {
    module.exports._private = {
        createTmpFile: createTmpFile
    }
};

test

   describe("resizer when data is path", function () {
        var testedModule, dir, sizesObj, tmpStub, writeStub250, writeStub500, writeStub650, writeStub750, callbackSpy, resizeStub, gmSubClassStub;
        var _private = require('../lib/modules/resizer')._private;
        console.log(_private.createTmpFile);

        before(function () {

            dir = "/tmp/rstest.png";

            sizesObj = [
                {name: "thumb", width: 250, height: 250},
                {name: "small", width: 500, height: 500},
                {name: "medium", width: 650, height: 650},
                {name: "large", width: 750, height: 750}
            ];


            writeStub250 = sinon.stub();
            writeStub500 = sinon.stub();
            writeStub650 = sinon.stub();
            writeStub750 = sinon.stub();

            resizeStub = sinon.stub();

            tmpStub = sinon.stub(_private, 'createTmpFile', function () { return "/temporary/"});

            gmSubClassStub = sinon.stub();

            callbackSpy = sinon.spy();

            testedModule = proxyquire('../lib/modules/resizer', {
                'gm': {subClass: sinon.stub().returns(gmSubClassStub)}
            });

        });

        after(function () {
            _private.createTmpFile.restore();
        });

        it("resize image and write to path", function () {

            resizeStub.withArgs(250, 250).returns({write:writeStub250});
            resizeStub.withArgs(500, 500).returns({write:writeStub500});
            resizeStub.withArgs(650, 650).returns({write:writeStub650});
            resizeStub.withArgs(750, 750).returns({write:writeStub750});

            // Stub is used when you just want to simulate a returned value
            gmSubClassStub.withArgs(dir).returns({resize:resizeStub});

            // Act - this calls the tested method
            testedModule.resize(dir, sizesObj, function (err) {
                callbackSpy.apply(null, arguments);
            });

            expect(writeStub250).has.been.called.and.calledWith("/temporary/thumb.png");
        });
});

Upvotes: 1

Views: 3069

Answers (2)

hyprstack
hyprstack

Reputation: 4228

So thanks to @robertklep comments, I decided to tackle the issue with proxyquire once again and came to the following solution which passes my tests.

I have changed the set-up by adding tmp.dirSync to proxyquire and force it to return the fake directory. I also removed the stub for the temporary directory.

describe("resizer when data is path", function () {
    var testedModule, dir, sizesObj, writeStub250, writeStub500, writeStub650, writeStub750, callbackSpy, resizeStub, gmSubClassStub;

    before(function () {

        dir = "/tmp/rstest.png";

        sizesObj = [
            {name: "thumb", width: 250, height: 250},
            {name: "small", width: 500, height: 500},
            {name: "medium", width: 650, height: 650},
            {name: "large", width: 750, height: 750}
        ];


        writeStub250 = sinon.stub();
        writeStub500 = sinon.stub();
        writeStub650 = sinon.stub();
        writeStub750 = sinon.stub();

        resizeStub = sinon.stub();

        gmSubClassStub = sinon.stub();

        callbackSpy = sinon.spy();

        testedModule = proxyquire('../lib/modules/resizer', {
            'gm': {subClass: sinon.stub().returns(gmSubClassStub)},
            'tmp': {
                'dirSync': function () {
                    return {
                        name: "/temporary"
                    }
                }
            }
        });
    });

    it("resize image and write to path", function () {

        resizeStub.withArgs(250, 250).returns({write:writeStub250});
        resizeStub.withArgs(500, 500).returns({write:writeStub500});
        resizeStub.withArgs(650, 650).returns({write:writeStub650});
        resizeStub.withArgs(750, 750).returns({write:writeStub750});

        // Stub is used when you just want to simulate a returned value
        gmSubClassStub.withArgs(dir).returns({resize:resizeStub});

        // Act - this calls the tested method
        testedModule.resize(dir, sizesObj, function (err) {
            callbackSpy.apply(null, arguments);
        });

        expect(writeStub250).has.been.called.and.calledWith("/temporary/thumb.png");
        expect(writeStub500).has.been.called.and.calledWith("/temporary/small.png");
        expect(writeStub650).has.been.called.and.calledWith("/temporary/medium.png");
        expect(writeStub750).has.been.called.and.calledWith("/temporary/large.png");

    });

    it("calls callbackSpy", function () {

        writeStub250.callsArgWith(1, null);
        writeStub500.callsArgWith(1, null);
        writeStub650.callsArgWith(1, null);
        writeStub750.callsArgWith(1, null);

        resizeStub.withArgs(250, 250).returns({write:writeStub250});
        resizeStub.withArgs(500, 500).returns({write:writeStub500});
        resizeStub.withArgs(650, 650).returns({write:writeStub650});
        resizeStub.withArgs(750, 750).returns({write:writeStub750});

        // Stub is used when you just want to simulate a returned value
        gmSubClassStub.withArgs(dir).returns({resize:resizeStub});

        // Act - this calls the tested method
        testedModule.resize(dir, sizesObj, function (err) {
            callbackSpy.apply(null, arguments);
        });

        expect(callbackSpy).has.been.called.and.calledWith('/temporary/');
    });
});

Upvotes: 0

robertklep
robertklep

Reputation: 203231

The issue is that you're stubbing _private.createTmpFile, which holds a reference to your private function. You're replacing the reference with the stub, but that's not the same as stubbing the original function.

Unless you actually use exports._private.createTmpFile (which is not something you want because exports._private only exists in the test environment) in your resize function, you won't be using the stub but the actual function (because createTmpFile still points to the original function).

Not sure if there's a clean way to solve this, to be honest.

Upvotes: 2

Related Questions