138

Say I'm spying on a method like this:

spyOn(util, "foo").andReturn(true);

The function under test calls util.foo multiple times.

Is it possible to have the spy return true the first time it's called, but return false the second time? Or is there a different way to go about this?

mikhail
  • 5,019
  • 2
  • 34
  • 47

2 Answers2

233

You can use spy.and.returnValues (as Jasmine 2.4).

for example

describe("A spy, when configured to fake a series of return values", function() {
  beforeEach(function() {
    spyOn(util, "foo").and.returnValues(true, false);
  });

  it("when called multiple times returns the requested values in order", function() {
    expect(util.foo()).toBeTruthy();
    expect(util.foo()).toBeFalsy();
    expect(util.foo()).toBeUndefined();
  });
});

However, there is something you must be careful about. There is another function with a similar spelling: returnValue without s. If you use that, Jasmine will not warn you, but it will behave differently.

Ocean
  • 2,882
  • 1
  • 18
  • 21
  • 33
    +1: this is an excellent feature. A word of warning, though - be careful not to forget the 's' in `.returnValues` - the two functions are obviously different, but passing multiple arguments to `.returnValue` doesn't throw an error. I don't want to admit how much time I wasted due to that one character. – The DIMM Reaper Jun 01 '16 at 20:16
  • @TheDIMMReaper, Thank you. I mention it now. – Ocean Nov 15 '16 at 06:14
  • Doing it in the beforeEach was important for me, not in the test (it) – Ian Feb 14 '19 at 11:13
  • No need to worry about spelling errors when using jasmine in TypeScript, of course. – Elliott Beach Sep 28 '19 at 19:42
  • @TheDIMMReaper that's exactly why I visit this post.... again and again, and again :D – Guntram Aug 19 '22 at 14:46
31

For older versions of Jasmine, you can use spy.andCallFake for Jasmine 1.3 or spy.and.callFake for Jasmine 2.0, and you'll have to keep track of the 'called' state, either through a simple closure, or object property, etc.

var alreadyCalled = false;
spyOn(util, "foo").andCallFake(function() {
    if (alreadyCalled) return false;
    alreadyCalled = true;
    return true;
});
voithos
  • 68,482
  • 12
  • 101
  • 116
  • 5
    We can extend this for more than 2 calls like so: var results = [true, false, "foo"]; var callCount = 0; spyOn(util, "foo").and.callFake(function() { return results[callCount++]; }); – Jack Apr 27 '16 at 14:21
  • 1
    This can be extended into a generic function like so: function returnValues() { var args = arguments; var callCount = 0; return function() { return args[callCount++]; }; } – Tomas Lieberkind Jul 27 '16 at 13:41
  • 1
    Why whole this code when you can just return results.shift() ? – ThaFog Oct 08 '18 at 08:54
  • how about when we return observabvles – Lijo May 20 '19 at 10:31
  • this option is more flexible, as we can throw error or return value depending on the call order. – 0bj3ct May 26 '20 at 18:44