thlorenz / proxyquire

🔮 Proxies nodejs require in order to allow overriding dependencies during testing.
MIT License
2.75k stars 100 forks source link

Unable to get proxyquire to work with node-ipInfo #120

Closed souly1 closed 8 years ago

souly1 commented 8 years ago

Hi, recently I've added the http://ipinfo.io node js wrapper (https://github.com/IonicaBizau/node-ipinfo) to my app and no matter what i've tried, I'm not able to get proxyquire+sinon to mock its behaviour. I assume its due to it exporting a function instead of an object, I've tired global=true flag written here, but no success. Any recommendations on how to approach this? Thanks

bendrucker commented 8 years ago

We need at least some code to be of any help

souly1 commented 8 years ago

Sure:

var ipinfo = require("ipinfo");
exports.bar = function(){
    ipinfo(ip, function (err, cLoc) {
        //cLoc holds the location data
    }
};

The spec class:

ipInfoMock = function(ip, callback){
    callback();
};
ipInfoMock['@global'] = true;
proxyquire("../../../node_modules/ipinfo/lib/index", ipInfoMock );
...
...
foo.bar();
assert.equal(ipInfoMock.prototype.constructor.called, true);

Fails

bendrucker commented 8 years ago

Right. Your problem is that you're not passing any of the right values to proxyquire. Proxyquire expects 1) string paths that point to your source code 2) objects where the keys are module ids/paths relative to your source and the values are the full/partial replacement. You need to pass './mysrc.js', {ipinfo: ipInfoMock}.

bendrucker commented 8 years ago

If it's easier to understand, you're expecting proxyquire to magically globally override modules that have already been loaded. That's nearly impossible to actually do. What's really happening is that proxyquire can override the module loader when loading your code and ensure that you get an opportunity to manipulate the top level and even the global dependency tree just for that call.

souly1 commented 8 years ago

Oops, really sorry, I've just pasted a wrong version of my attempt, here's and updated version of the proxyquire call:

proxyquire("../../../modules/foo", {'ipinfo': ipInfoMock} );

This is what fails for me and I'm unclear as to why

souly1 commented 8 years ago

Adding here code from ipInfo's index.js, pretty short:

var IpInfo = module.exports = function (type, callback) {

    var url = null;

    if (typeof type === "function") {
        url = IpInfo.HOSTNAME + "json";
        callback = type;
    } else if (IpInfo.IP_REGEX.test(type)) {
        url = IpInfo.HOSTNAME + type + "/json";
    } else {
        url = IpInfo.HOSTNAME + type;
    }

    JsonRequest(url, callback);
};
IpInfo.HOSTNAME = "http://ipinfo.io/";
IpInfo.IP_REGEX = /^\b(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b$/;
bendrucker commented 8 years ago

That should work ok, but why aren't you assigning the proxyquire value to anything? Can I get something I can run?

souly1 commented 8 years ago

Sure, https://ide.c9.io/souly1/ipinfo_issue

Click run on displaying geoSpec.js, all should be ready (mocha will run).

Thanks!

bendrucker commented 8 years ago

That's not how proxyquire works. You need to assign the return value of proxyquire, not expect the ability to manipulate requires that already happened.

souly1 commented 8 years ago

humph, I'm pretty sure it worked for me in other modules... seeing what i meant, do you think there is a way to run the same result i want from the test?

bendrucker commented 8 years ago

I don't know what result you're expecting. As long as you assign the proxied module instead of assuming some global manipulation will happen (it won't) you should be okay.

souly1 commented 8 years ago

OK, Finally understood what you were saying, fixed the c9 example. If fixing the example written above basically all that was required was:

foo = proxyquire("../../../modules/foo", {'ipinfo': ipInfoMock} );

thanks again for the patience @bendrucker Cheers