Reputation: 157
I'm unit testing JavaScript with Jasmine and I am running into some problems.
I have a large file to test and it has a lot of dependencies and those dependencies have their own dependencies. Because of said dependencies I want to mock all I can. There lies the problem. How can I mock a constructor so that it includes the methods that belong to it?
Lets say I'm testing a method createMap
of class Map
:
In that createMap
method it calls for Layers
class constructor using
var layers = new Layers()
I'm spying on it using
spyOn(window, 'Layers').and.callThrough()
That works fine but later in the createMap
method it calls for layers.addLayer()
where addLayer
is a method of Layers
class. Problem is that because I mocked the Layers
call it doesn't recognize the addLayer
method.
Is there a way to mock it so that it includes all the methods of the called class or is my only option to stub the whole Layers
class or not mock it?
Or what would be a good way to handle this? I've tried to spyOn(Layers, 'addLayer')
but there it says that no method addLayer
is found.
I'm sorry if it's confusing a bit. I had trouble thinking how should I ask it.
Upvotes: 5
Views: 7300
Reputation: 787
If you ever transpile class syntax to a es3 or another pre-2015 dialect you will discover something interesting.
class a {
constructor(){
...
}
index()
{
...
}
}
Becomes:
var a = /** @class */ (function () {
function a() {
...
}
a.prototype.index = function () {
...
};
return a;
}());
This same implementation is used by later standards but masked by the 2015 class syntax. In other words a.index
doesn't exist instead it's defined as a.prototype.index
. Thus you need spyOn(a.prototype, 'index')
to spy on it.
Change spyOn(Layers, 'addLayer')
to spyOn(Layers.prototype, 'addLayer')
Upvotes: 1
Reputation: 44889
IMO, it's unnecessary to spy on window
, since you can easily shadow the variable in local scope by creating a spy object with the same name:
describe('Map', function () {
var Layers;
beforeEach(function () {
Layers = function () {
// alternatively, you could move this to Layers.prototype
this.addLayers = jasmine.createSpy('Layers#addLayers');
};
});
/* ... */
});
If you want an automatic mocking and using CommonJS modules, you may try Jest framework, which is built on top of Jasmine.
Upvotes: 6
Reputation: 6143
Let's talk in terms of example classes you have provided.
You're writing a test suite for Map
. All its dependencies (in example we have only Layer
) MUST be mocked. Because in a unit test you're supposed to test one layer, as small functionality as possible. It means that you should provide such a mocked Layer
constructor that exposes interface used in Map
. For example:
function Layers() {
this.addLayer = sinon.spy();
}
In this test suite only Map
class should remain "real". I.e. it's code must not be altered. And with such mockups like Layer
you make sure that you do not trigger any interaction with real-code dependencies (own-written dependencies should be tested in a different test suite, also make sure you don't try to test framework functions, like $tate.resolve
, $inject
etc.). If class Map
is complicated and has multiple dependencies, investigate sinon features that help automate this process, for example sinon.mock
Upvotes: 2