Spying on a constructor using Jasmine

73,587

Solution 1

flipCounter is just another function, even if it also happens to construct an object. Hence you can do:

var cSpy = spyOn(window, 'flipCounter');

to obtain a spy on it, and do all sorts of inspections on it or say:

var cSpy = spyOn(window, 'flipCounter').andCallThrough();
var counter = flipCounter('foo', options);
expect(cSpy).wasCalled();

However, this seems overkill. It would be enough to do:

var myFlipCounter = new flipCounter("counter", options);
expect(myFlipCounter).toBeDefined();
expect(myFlipCounter.getValue(foo)).toEqual(bar);

Solution 2

I would suggest using jasmine.createSpyObj() when you want to mock objects with properties that need to be spied on.

myStub = jasmine.createSpyObj('myStub', ['setValue']);
spyOn(window, 'flipCounter').andReturn(myStub);

This tests interactions with the expected flipCounter interface, without depending on the flipCounter implementation.

Solution 3

The following does not rely on 'window'. Lets say this is the code you want to test -

function startCountingFlips(flipCounter) {
    var myFlipCounter = new flipCounter("counter", {inc: 23, pace: 500});
}

Your test could be -

var initSpy = jasmine.createSpy('initFlipCounter');
var flipCounter = function(id, options) {
    initSpy(id, options);
}
startCountingFlips(flipCounter);
expect(initSpy).toHaveBeenCalledWith("counter", {inc:23, pace:500});

Solution 4

You have to implement a fake constructor for flipCounter that sets the setValue property to a spy function. Let's say the function you want to test is this:

function flipIt() {
  var myFlipCounter = new flipCounter("counter", {inc: 23, pace: 500});
  myFlipCounter.setValue(100);
}

Your spec should look like this:

describe('flipIt', function () {
  var setValue;
  beforeEach(function () {
    setValue = jasmine.createSpy('setValue');
    spyOn(window, 'flipCounter').and.callFake(function () {
      this.setValue = setValue;
    });
    flipIt();
  });
  it('should call flipCounter constructor', function () {
    expect(window.flipCounter)
      .toHaveBeenCalledWith("counter", {inc: 23, pace: 500});
  });
  it('should call flipCounter.setValue', function () {
    expect(setValue).toHaveBeenCalledWith(100);
  });
});

Solution 5

My version to test a constructor is to spy on the prototype:

spyOn(flipCounter.prototype, 'setValue').and.callThrough();
var myFlipCounter = new flipCounter("counter", {inc: 23, pace: 500});
expect(flipCounter.prototype.setValue).toHaveBeenCalledTimes(1);
Share:
73,587
gerky
Author by

gerky

Linux enthusiast.

Updated on July 05, 2022

Comments

  • gerky
    gerky almost 2 years

    I am using Jasmine to test if certain objects are created and methods are called on them.

    I have a jQuery widget that creates flipcounter objects and calls the setValue method on them. The code for flipcounter is here: https://bitbucket.org/cnanney/apple-style-flip-counter/src/13fd00129a41/js/flipcounter.js

    The flipcounters are created using:

    var myFlipCounter = new flipCounter("counter", {inc: 23, pace: 500});
    

    I want to test that the flipcounters are created and the setValue method is called on them. My problem is that how do I spy on these objects even before they are created? Do I spy on the constructor and return fake objects? Sample code would really help. Thanks for your help! :)

    Update:

    I've tried spying on the flipCounter like this:

    myStub = jasmine.createSpy('myStub');
    spyOn(window, 'flipCounter').andReturn(myStub);
    
    //expectation
    expect(window.flipCounter).toHaveBeenCalled();
    

    Then testing for the setValue call by flipCounter:

    spyOn(myStub, 'setValue');
    
    //expectation
    expect(myStub.setValue).toHaveBeenCalled();
    

    the first test for initializing flipCounter is fine, but for testing the setValue call, all I'm getting is a 'setValue() method does not exist' error. Am I doing this the right way? Thanks!

  • ggozad
    ggozad over 12 years
    No spying on a spy like that is not a good idea ;) I would either use the second approach I took above, test and spy separately on setValue to make sure that works too.
  • ggozad
    ggozad over 12 years
    In any case when you do spyOn(window, 'flipCounter').andReturn(myStub); you have replaced your constructor with something that does nothing. You must either callThrough or replicate the constructor while testing it.
  • ggozad
    ggozad over 12 years
    If ALL you want to do is check that setValue was called, don't spy on the constructor. Rather spy (and callThrough()) on setValue. You can then check that it was called, check its arguments, and still have the object.
  • sMoZely
    sMoZely over 10 years
    No need to use an extra library when Jasmine has capable mocks built in. Sinon.js is useful for other things though
  • Vinicius Pinto
    Vinicius Pinto almost 10 years
    The syntax in Jasmine 2.0 is spyOn(foo, 'getBar').and.callThrough();
  • ritmatter
    ritmatter over 9 years
    If you are running this code server-side, and there is no window, what can you use to refer to the environment where new flipCounter() is called?
  • nbering
    nbering almost 9 years
    I had issues spying on constructors. Even calling through did not result in the expected object, and then resulted in errors further down the code path when something tried to call functions from the object's prototype.
  • Sean
    Sean over 8 years
    @ritmatter: use global instead of window if running server-side
  • Jared Deckard
    Jared Deckard almost 8 years
    I prefer this answer over @ggozad's answer, because it keeps external modules isolated from tests and uses a built-in jasmine utility designed specifically for mocking instance-like objects.
  • Danny Staple
    Danny Staple almost 8 years
    Hmm - this doesn't quite make sense. The example at the bottom of it is odd. I've certainly wanted to create spies to inject this into other code - ie code that will construct something gets a spy instead so you can manipulate it's behaviour under test.
  • dinvlad
    dinvlad almost 7 years
    +1, this approach doesn't seem to work in more complicated scenarios, such as TypeScript classes. There's no class object defined in the global/window scope. How could one work around that?
  • Daniel Hilgarth
    Daniel Hilgarth over 6 years
    Nice, thanks. This also works perfectly with built-in objects like XMLHttpRequest
  • HolgerJeromin
    HolgerJeromin over 6 years
    For ajax mockup i use this one: jasmine.github.io/2.2/ajax.html