Closed Alexsey closed 8 years ago
Not sure if I have the same problem but something similar is happening to me.
Consider the following code:
const auth = proxyquire('./lib/middleware/auth', {
'lib/model': {
AuthTokens: {
refresh: (token) => (done) => done(null, token === 'auth-token')
}
}
})()
AuthTokens.refresh
is permanently mocked even when require('./lib/model').AuthTokens.refresh
, am I missing something or this is not how it's supposed to work?
Have you tried to use noPreserveCache
?
You definitely need noPreserveCache, we can reopen if that's not it
@bendrucker it's the other way around, when I call require
it returns the proxyquired mocked version. Not sure if my issue is separated.
Same thing. If you don't tell proxyquire not to use the require cache it will.
@bendrucker Look at example in initial message. There is noPreserveCace
but it works bad
And it behave differently for function that is directly in export object, and with one that is not directly on in
@bendrucker ok, but I would consider changing this behavior anyway, I can't think of any case where you want proxyquire to globally mock a library and affect require
, to me that's the whole point and reason why I am using it. Shouldn't it be the other way around?
If I am wrong I would personally rather export all the dependencies I have to mock in the test suite and have more control.
You may want to consider a .restore()
function if the reason this is behaving like this is purely a technicality.
@bendrucker could you please look at the initial problem and answer why this it not a bug, or reopen this issue?
There is a link at the start of initial post on another project on GitHub with code that represents problem
Questions are
noPreserveCace
do nothing?g
and h
behave differently?Don't have time to look into it this week, will reopen in case anyone else wants to contribute
I may have a related case to this, or it might be something that I'm misunderstanding about proxyquire.
In my case, I'm testing some methods normally, but then I need to stub those methods later.
import * as methods from './some-module'
import proxyquire from 'proxyquire'
proxyquire.noPreserveCache()
... do some tests on `methods`
... later
const mock = proxyquire('./some-module', {
someMethod: sinon.spy()
})
However, mock
ends up being equal to methods
, and therefore I can't spy on the internals.
I think this may be related. I have been trying to understand if there was a way around this behavior.
var proxyquire = require('proxyquire').noPreserveCache();
var expect = require('expect.js');
var TestModule = '../index.js';
describe('Test', function(){
var dependencyMock;
var test;
before(function(){
dependencyMock = function(){
this.doStuff = function(callback){
return callback(null, 'blah');
};
};
var mocks = { './dependency.js': dependencyMock };
var Test = proxyquire(TestModule, mocks);
test = new Test();
});
it('does things', function(done){
test.doSomething(function(err, data){
expect(data).to.be('blah');
done();
});
});
describe('dependency returns banana', function(){
before(function(){
dependencyMock = function(){
this.doStuff = function(callback){
return callback(null, 'banana');
};
};
});
it('does banana things', function(done){
test.doSomething(function(err, data){
expect(data).to.be('banana');
done();
});
});
});
});
The test for banana fails because it still comes back with the original mocked data of 'blah'.
@cbshakumar Just before 'does banana things' test runs both of your before
functions would be executed (Mocha
works that way) so doStuff
would be reassigned twice. Probably Mocha
execute firstly inner bofore
and than outer before
that is why in your test it returns 'blah' in stead of 'banana'. Log/debug your code to check the order of before
s execution
@kwhitaker you are trying to pass as second parameter of proxyquire
call object with key that is the name of the method. proxyquire
doesn't work that way: keys should be the names of packages that require module in first argument and values should be object with keys that are names of methods in required packages. Look at this section of the docs
@Alexsey
before is run only once for each describe block it is in. This is not the beforeEach function from mocha. But you can see from the repl/debug that it has already ran through the outer before statement by the time it has gotten to the inner one.
@Alexsey thanks for clearing that up. I think I might need something like https://www.npmjs.com/package/rewire for this case.
@cbshakumar Yes, sorry, probably I confused them. Any way your problem is definitely in fact that you are reassigning your local dependencyMock
variable with a new function while proxyquire
continues to work with an old one. There is now way in JavaScript proxyquire
can detect change of a target of a link
@Alexsey right, I would imagine that just modifying the object instance itself, proxyquire should pick this up since it has the object reference still and I have no preserve cache set. How else can I get proxyquire to automatically pickup the modified object reference?
@cbshakumar proxyquire
itself can't help you. And there is no way in JS to send a pointer. But there are some tricks you can do. First I can imagine is to change mocks
to
var mocks = { './dependency.js': function () {
return dependencyMock.apply(this, arguments)
}};
I was able to get tests passing by changing them to use an internal instance for the mock.
var proxyquire = require('proxyquire').noPreserveCache();
var expect = require('expect.js');
var TestModule = '../index.js';
describe('Test', function(){
var dependencyMock;
var instance;
var test;
before(function(){
console.log('outer');
dependencyMock = function(){
this.doStuff = function(callback){
instance.doStuff(callback);
};
};
instance = {
doStuff: function(callback){
return callback(null, 'blah');
}
};
var mocks = { './dependency.js': dependencyMock };
var Test = proxyquire(TestModule, mocks);
test = new Test();
});
it('does things', function(done){
test.doSomething(function(err, data){
expect(data).to.be('blah');
done();
});
});
describe('dependency returns banana', function(){
before(function(){
instance.doStuff = function(callback){
callback(null, 'banana');
}
});
it('does banana things', function(done){
test.doSomething(function(err, data){
expect(data).to.be('banana');
done();
});
});
});
});
Maybe we should have some kind of abstraction/helper code to remove the cruft of having to do this.
Yep, that is a similar way. I don't see how such helper could be a part of proxyquire
... but if you do - you should probably create a separate issue with suggestion as fare as we figure out that it is not about the bug from the initial post
Sure
Thank you for the diligent effort in reproducing this @Alexsey
I just dug in for a bit and was able to identify the issue. The stubs (child of a child) were still stuck in the require cache even though the child itself was deleted. I've made an addition to delete those as well. I've also incorporated a virtual identical unit test to your repro project and verified that the changes pass your tests.
This is great! Thank you for fixing it =)
This project contains working demonstration of the bug. Setup of a problem is
foo
that requires modulebar
that contains object that contains functiong
. We need to stubg
foo
and stabbar
with empty objectfake
g
onfake
(g
is not directly onbar
, so in is likefake.f.g = ...
)foo
while stubbingbar
with new empty objectg
is still overriddenFor comparison in the root of
bar
there is also functionh
(not wrapped withf
object) and it acts as expectednoPreserveCache
is active. So any changes on modules should be dropped on every newproxyquire
. But it is notUPD: one of the possible solutions is to clean
require.cache
ofbar
before re-proxyquiringfoo
(likedelete require.cache[require.resolve('./bar')]
). Not the thing I really want to do while writing simple tests...UPD: this solution is even worse than it looks: if required
foo
requirebar
which requiresbaz
then you need to clean cache forbaz
. And you Definitely don't want to know aboutbaz
while you are testingfoo
May be I'm missing something and there is a better way to restore modules state between tests? For now it looks like much easier to stab all methods with
sinon
'ssandbox