5

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.

moffeltje
  • 4,521
  • 4
  • 33
  • 57
sergei
  • 157
  • 3
  • 9

3 Answers3

6

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.

Community
  • 1
  • 1
Pavlo
  • 43,301
  • 14
  • 77
  • 113
  • This might sound like a stupid follow up question but if I use this method wouldn't the createMap method call `var layers = new Layers()` and it would fail as it's an object and not a function/constructor. – sergei Jul 01 '15 at 18:22
  • @MikeJones you're right, it needs to be a function. See updated answer. – Pavlo Jul 02 '15 at 10:46
2

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

Kirill Slatin
  • 6,085
  • 3
  • 18
  • 38
1

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')