Closed zardaloop closed 9 years ago
You didn't say why you want to stub out $window
but I can see in your code that you want to fake one of its properties, sessionStorage
.
As you may know, $window
is a kind of proxy for the browser window
object. You write your application code to reference $window
instead of window
so that it is easier to test the parts of your application that need access to browser window features.
$window
has the simplest possible implementation. It simply returns the browser's window
object:
function $WindowProvider() {
this.$get = valueFn(window); // valueFn returns a function that returns the window object
}
Now window
is the biggest object in all of JavaScript. There is no way bard (or anyone) can anticipate everything that anyone might want to fake about it. We're not going to extend bard for $window
mocking.
But it's really easy for you to fake exactly what you want ... by replacing Angular's $window
implementation with your own.
Let's take what I believe to be your case. I think you want to fake behaviors of the window.sessionStorage
object. Start by creating a fake sessionStorage
that does what your test(s) need. I don't know what that is exactly but it might be:
var storedItem = ...; // whatever you want it to be
var fakeSessionStorage = {
getItem: sinon.stub().returns(storedItem),
setItem: sinon.spy(function setItem(key, obj) {storedItem = obj;})
}
Now create a fake window object that supports only what your tested component needs.
var fakeWindow = {
sessionStorage: fakeSessionStorage
};
Now register your fake window during test module setup:
bard.module('app.customers', function ($provide) {
$provide.value('$window', fakeWindow); // replaces ng's $window w/ the fake
});
Notice that I'm using
bard.module
which fakes toastr for you automatically.
Putting it all together we get:
beforeEach(function() {
var storedItem = ...; // whatever you want it to be initially
var fakeSessionStorage = {
getItem: sinon.stub().returns(storedItem),
setItem: sinon.spy(function setItem(key, obj) {storedItem = obj;})
}
bard.module('app.customers', function ($provide) {
$provide.value('$window', fakeWindow); // replaces ng's $window w/ the fake
});
bard.inject(this, '$location', '$rootScope', '$state', '$templateCache', '$window');
});
If you find yourself faking sessionStorage
in multiple test files, you'll want to pull that effort into a spec helper function ... perhaps like this:
function fakeWindowForSessionStorage(storedItem) {
var fakeSessionStorage = {
getItem: sinon.stub().returns(storedItem),
setItem: sinon.spy(function setItem(key, obj) {storedItem = obj;})
};
var fakeWindow = {
sessionStorage: fakeSessionStorage
};
return function ($provide) {
$provide.value('$window', fakeWindow);
};
}
And you use it like this:
beforeEach(function() {
bard.module('app.customers', mySpecHelper.fakeWindowForSessionStorage());
bard.inject(this, '$location', '$rootScope', '$state', '$templateCache', '$window');
});
I strongly recommend replacing the
$window
definition itself as we do here rather than replacing/overriding members of$window
; if you do the latter, you'll be replacing members of the global browser object, ruining them for all subsequent tests. Of course you could restore the original member after each test with anafterEach
but that's a lot of work. Stick with my approach.
Ward many thanks for taking time to explain this to me, I really appreciate that. It all make perfect sense. I am only not clear about one thing. Does that mean if I use $provide.provider like this:
$provide.provider('$window', function () {
/* jshint validthis:true */
this.$window = sinon.stub();
this.$get = function () {
return {
sessionStorage: sinon.stub({
token: 'someToken'
})
};
};
});
It will override the $window for all the other subsequent tests whereas if I only use $provide.value like this:
var fakeSessionStorage = {
getItem: sinon.stub().returns(storedItem),
setItem: sinon.spy(function setItem(key, obj) {storedItem = obj;})
};
var fakeWindow = {
sessionStorage: fakeSessionStorage
};
return function ($provide) {
$provide.value('$window', fakeWindow);
};
it will enable me to fake it for only the bard.module that I am passing on thefakeWindow and afterward the actual $window will be used for the other bard.module if I don't fake it and simply inject $window?
Both approaches replace the ng $window
service definition for each test separately. There is no cross test pollution and test outside of the scope of the describe
are unaffected.
The risk of crosps test pollution arises when you do something like $window.sessionStorage=fakeStorage; // don't do this!
with the original $window
service. That would override the DOM
window.sessionStorage
which is a global variable.
BTW, the following line in your code doesn't do what you think it does and is immediately replaced anyway at runtime when Ng invkes the $get
this.$window = sinon.stub();
I see .... :) Many thanks Ward. Really helpful points. I will keep them all in mind. Cheers
Closing because there does not appear to be an outstanding issue
Hi Ward, I need help with bardjs and I would highly appreciate if you could help me.
I am using John Papa's gulp pattern (https://github.com/johnpapa/gulp-patterns) where he uses bardjs. I want to fake $window because I have some stuff stored in the sessionstorage which is used within the application. What would be the best way of adding additional fake providers?
I know that I can go ahead and add a file like this (https://github.com/johnpapa/ng-demos/blob/master/cc-bmean/src/client/test/lib/specHelper.js) and there define the fakeWindow within that file like this:
and then for example in this file (https://github.com/johnpapa/gulp-patterns/blob/master/src/client/app/customers/customers.route.spec.js), instead of having :
do something like this:
obviously this approach will do exactly what I need, but it is getting drifted away from bardjs.
So my question is how would you do it ?
Many thanks in advance.
Farzan