-
-
Save keithamus/4130885 to your computer and use it in GitHub Desktop.
define([], function () { | |
return function HypotheticalHelperMethod() { | |
doSomeOtherStuff(); | |
} | |
}); | |
define(["helperMethod"], function (helperMethod) { | |
MyClass = function () { | |
this.init() | |
} | |
MyClass.prototype.init = function () { | |
helperMethod(1, 2, 3); | |
} | |
}); |
require(["helperMethod", "myClass"], function (helperMethod, myClass) { | |
describe("MyClass tests", function () { | |
it("calls `helperMethod` upon init", function () { | |
// here i need to spyOn helperMethod, but if I set helperMethod | |
// to a jasmime spy: | |
helperMethod = jasmine.createSpy("helperMethod"); | |
// I've altered my reference, and MyClass still has the original | |
// HypotheticalHelperMethod function. | |
var instance = new MyClass(); | |
expect(helperMethod).toHaveBeenCalledWith(1, 2, 3); | |
}); | |
}); | |
}); |
I don't think this is a problem with Jasmine so much as a limitation of Javascript itself since functions are passed by value rather than by reference.
Or indeed a problem with require, which I realise seems to be where you're actually pointing the finger.
I can't see any way you could expect a modifying a reference to a function in one place to modify the "same*" function in another, unless that function is referenced as a property of an object.
[* not the same]
Something to try:
Use themap config to point all consumers of 'helperMethod' to an adapter:
requirejs.config({
map: {
'*': {
helperMethod: 'helperMethodSpy'
},
'helperMethodSpy': {
helperMethod: 'helperMethod'
}
}
});
Then, helperMethodSpy.js can do the method interception:
define(['helperMethod'], function (hm) {
//Wrap hm however you want
return wrappedHmFn;
});
If you only need do to this for one test/if you want to mock different things, then you can use the multiversion/context support to create different buckets of modules for each test.
Thanks for the replies.
helperMethod still needs to be tested, and so altering the config means the test that requires helperMethod also has the spied version, correct? This method seems like a large, sweeping change to the require config given this problem happens tens of times across a large codebase.
Not sure I follow -- for tests that do not need the altered helperMethod, create new multiversion contexts for those tests that either do or do not have that map config applied.
Put another way, how would you do the testing of a non-modified helperMethod and a spied version of it, if AMD modules were not involved? It would seem to me that you would have to have two different test runs of the code, which is what is provided by the multiversion contexts. But I could very well not understand the test methodology involved.
Maybe I haven't explained my self best, so here goes:
I think the real problem I have is because of the declarative nature of require, and the fact that a module is loaded when its dependencies are met, rather than when it itself is called as a dependency. Because of these issues it becomes really difficult to get code before dependency resolution, which is the typical pattern I'd use to spy on something.
If AMD was not involved I would have to either expose the module via name spacing (which makes it easy to spy on) or if I was using some Node Style module loader which requires dependencies imperatively I could stub over the require function to reroute it (aka behaviour like Syringe or Rewire). Both of these means I can mock or spy a module imperatively -- and inline to the tests -- and restore old functionality when I'm done.
Meanwhile AMDs solution seems to be to provide an additional layer of complexity - in that if I want to spy on a module I need to alter the config and set up spies independent of tests. I was hoping for something which could be managed purely inside the test environment, rather than having additional config files to manage tests.
Ah, OK. If you want to just cram in a modification after a module has been created but before listeners are given a reference to it, you can use the semi-private onResourceLoad API:
https://github.com/jrburke/requirejs/wiki/Internal-API:-onResourceLoad
Note this API is always subject to change. It has been stable for a while, but no guarantees for the future, even though I have no immediate plans to change it.
To modify the value passed to modules dependent on the current module, this would work:
requirejs.onResourceLoad = function (context, map, depArray) {
var id = map.id,
internalModule = context.registry[id],
existingExport = context.defined[id];
//modify existing export here
//If the modification is a modification to the base export (like
//if the export was a function) and not just a property
//modification on the export, hard set the new module value:
internalModule.exports = context.defined[id] = newExportValue;
};
This onResourceLoad definition should be done after require.js loads, but before any module loading is done, at least for modules that you want to intercept.
@keithamus did you find a way to test that your helperMethod is called?
Btw I am aware that I could turn helperMethod into an object and spy on a property of that, and I'm also aware I could add helperMethod to MyClass as a property to expose it to the tests there, but neither of these solutions are satisfactory to me.