sergei
sergei

Reputation: 157

Unit testing with Jasmine, mocking a constructor

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

Answers (3)

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

Pavlo
Pavlo

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

Kirill Slatin
Kirill Slatin

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

Related Questions