37

I have a function

var data = {};
var myFunc = function() {
  data.stuff = new ClassName().doA().doB().doC();
};

I'd like to test that doA, doB, and doC were all called.

I tried spying on the instance methods like this

beforeEach(function() {
  spyOn(ClassName, 'doA');
};
it('should call doA', function() {
  myFunc();
  expect(ClassName.doA).toHaveBeenCalled();
});

but that just gives me a "doA() method does not exist" error.

Any ideas?

Bojan Komazec
  • 9,216
  • 2
  • 41
  • 51
sfletche
  • 47,248
  • 30
  • 103
  • 119

2 Answers2

57

Where you went wrong was your understanding of how to refer to methods in JavaScript in a static context. What your code is actually doing is spying on ClassName.doA (that is, the function attached to the ClassName constructor as the property doA, which is not what you want).

If you want to detect when that method gets called on any instance of ClassName anywhere, you need to spy on the prototype.

beforeEach(function() {
  spyOn(ClassName.prototype, 'doA');
});
it('should call doA', function() {
  myFunc();
  expect(ClassName.prototype.doA).toHaveBeenCalled();
});

Of course, this is assuming that doA lives in the prototype chain. If it's an own-property, then there is no technique that you can use without being able to refer to the anonymous object in myFunc. If you had access to the ClassName instance inside myFunc, that would be ideal, since you could just spyOn that object directly.

P.S. You should really put "Jasmine" in the title.

mooiamaduck
  • 2,066
  • 12
  • 13
  • We're facing a similar challenge, in which the `ClassName.prototype.doA` method does fire but instance fields are not instantiated within an instance of `ClassName`. The instance fields are populated in the constructor, but unfortunately, they are all undefined when we're spying on one of the instance methods. – Shaun Luttin Aug 13 '16 at 02:09
  • Wow, great answer. I have always used https://github.com/mfncooper/mockery to mock the entire constructor at the require module cache. Not only is it more verbose, it sometimes leads to tests having really hard to grok\debug problems. This is a great solution that is much easier to understand. Jasmine really should include this example in their docs. – Eric Rini Aug 26 '16 at 13:04
  • Perfect, works well for me. I was about creating a question about this same issue but you saved my day. Thank you @mooiamaduck – Eyong Kevin Enowanyo Mar 23 '21 at 15:31
  • A bit hacky but worked for me as well. Bottom line - method returns whatever is expected. Thanks for sharing it. – Sgryt Jun 21 '22 at 02:15
7

Let’s do some code refactoring as we want implement constructor injection pattern as James Shore mentions that:

Dependency injection means giving an object its own instance variables. Really. That’s it.

var data = {};
var stuff = new ClassName()

var myFunc = function(stuff) { // move step of creation new object outside
  data.stuff = stuff.doA().doB().doC();
};

And time for some tests

function ClassName() {
}

var data = {};
var stuff = new ClassName()

var myFunc = function(stuff) {
  data.stuff = stuff.doA().doB().doC();
};


describe('stub for ClassName implementation', function() {
  var stubStuff = {
    doA: function() {
      return stubStuff
    },
    doB: function() {
      return stubStuff
    },
    doC: function() {
      return stubStuff
    }
  }

  beforeEach(function() {
    spyOn(stubStuff, 'doA').and.callThrough();
  });

  it('calls "doA" on "myFunc" exection', function() {
    myFunc(stubStuff);
    expect(stubStuff.doA).toHaveBeenCalled();
  });
});
<link href="//safjanowski.github.io/jasmine-jsfiddle-pack/pack/jasmine.css" rel="stylesheet" />
<script src="//safjanowski.github.io/jasmine-jsfiddle-pack/pack/jasmine-2.0.3-concated.js"></script>
Krzysztof Safjanowski
  • 7,292
  • 3
  • 35
  • 47