91

How do we clear the spy in a jasmine test suite programmatically? Thanks.

beforeEach(function() {
  spyOn($, "ajax").andCallFake(function(params){
  })
})

it("should do something", function() {
  //I want to override the spy on ajax here and do it a little differently
})
trivektor
  • 5,608
  • 9
  • 37
  • 53
  • 4
    Sure you chose the right "correct" answer? – hgoebl Feb 10 '15 at 08:12
  • 3
    As of Jasmine 2.0 ["A spy only exists in the `describe` or `it` block in which it is defined, and will be removed after each spec."](https://jasmine.github.io/2.0/introduction.html#section-Spies) – AJP May 24 '17 at 21:10

10 Answers10

141

setting isSpy to false is a very bad idea, since then you spy on a spy and when Jasmine clears the spies at the end of your spec you won't get the original method. the method will be equal to the first spy.

if are already spying on a method and you want the original method to be called instead you should call andCallThrough() which will override the first spy behavior.

for example

var spyObj = spyOn(obj,'methodName').andReturn(true);
spyObj.andCallThrough();

you can clear all spies by calling this.removeAllSpies() (this - spec)

theUtherSide
  • 3,338
  • 4
  • 36
  • 35
Alissa
  • 1,824
  • 1
  • 12
  • 15
  • 1
    This is imho the best solution. – Matthieu Riegler Aug 12 '14 at 09:40
  • I prefer to say that setting isSpy to false is a bad thing to do. I like Andreas K's out of the box thinking. – KSev Sep 22 '14 at 20:22
  • 26
    note that in jasmine 2 it's spyObj.and.callThrough(); – Boris Charpentier Jan 16 '15 at 15:15
  • 4
    In the case you don't want to call the original function but just override the original spy for a nested describe, do so by returning another value using "spyObj.and.returnValue('some other value');" – Ilker Cat Nov 24 '15 at 11:27
  • Not sure if this has always been the case but in jasmine 2.0 the spy is removed automatically per each spec (i.e. after each `it` is called). https://jasmine.github.io/2.0/introduction.html#section-Spies – marksyzm Jan 16 '17 at 12:10
  • 3
    With the new syntax: `spyOn(obj,'methodName').and.returnValue(true);` set the spy to return a value. Then in a further test you want it to spy the real function like so: `obj.methodName.and.callThrough();` – Watchmaker Jul 10 '17 at 16:08
  • @Alissa, can you say more about "removeAllSpies()"? I am not sure how it should be implemented. – LHM Oct 29 '19 at 20:36
47

I think that's what .reset() is for:

spyOn($, 'ajax');

$.post('http://someUrl', someData);

expect($.ajax).toHaveBeenCalled();

$.ajax.calls.reset()

expect($.ajax).not.toHaveBeenCalled();
aglasser
  • 3,059
  • 3
  • 13
  • 10
gxc
  • 4,946
  • 6
  • 37
  • 55
  • 14
    So all this does is reset the tracking state, if you're looking to restore default behavior this is not going to help. – FilmJ Jun 12 '13 at 17:21
  • 20
    Note that this has changed to `mySpy.calls.reset()` in Jasmine 2. – Henrik N Aug 26 '14 at 08:48
  • 1
    `mySpy.calls.reset()` resets the counter of times the spy had been called. You can check it with `expect(spy).toHaveBeenCalledTimes(1)` – Spiral Out Apr 11 '18 at 15:35
  • 2
    How does an answer that completely incorrectly answers the question have nearly 40 upvotes? The question asks how to override the `andCallFake`, not how to reset the call counter. – Vala Oct 15 '19 at 09:00
21

So spies are reset automatically between specs.

You actually do not get the benefit of "restoration" of the original function if you use andCallFake() within a beforeEach() and then attempt to forcibly change it within a spec (which is likely why it tries to prevent you from doing so).

So be careful, especially if your spy is being set on a global object such as jQuery.

Demonstration:

var a = {b:function() { return 'default'; } }; // global scope (i.e. jQuery)
var originalValue = a.b;

describe("SpyOn test", function(){
  it('should return spy1', function(){
    spyOn(a, 'b').andCallFake(function(params) {
      return 'spy1';
    })
    expect(a.b()).toEqual('spy1');
  });

  it('should return default because removeAllSpies() happens in teardown', function(){
    expect(a.b()).toEqual('default');
  });


  it('will change internal state by "forcing" a spy to be set twice, overwriting the originalValue', function(){
    expect(a.b()).toEqual('default');

    spyOn(a, 'b').andCallFake(function(params) {
      return 'spy2';
    })
    expect(a.b()).toEqual('spy2');

    // This forces the overwrite of the internal state
    a.b.isSpy = false;
    spyOn(a, 'b').andCallFake(function(params) {
      return 'spy3';
    })
    expect(a.b()).toEqual('spy3');

  });

  it('should return default but will not', function(){
    expect(a.b()).toEqual('default'); // FAIL

    // What's happening internally?
    expect(this.spies_.length).toBe(1);
    expect(this.spies_[0].originalValue).toBe(originalValue); // FAIL
  });

});

describe("SpyOn with beforeEach test", function(){
  beforeEach(function(){
    spyOn(a, 'b').andCallFake(function(params) {
      return 'spy1';
    })
  })

  it('should return spy1', function(){
    // inspect the internal tracking of spies:
    expect(this.spies_.length).toBe(1);
    expect(this.spies_[0].originalValue).toBe(originalValue);

    expect(a.b()).toEqual('spy1');
  });

  it('should return spy2 when forced', function(){
    // inspect the internal tracking of spies:
    expect(this.spies_.length).toBe(1);
    expect(this.spies_[0].originalValue).toBe(originalValue);

    // THIS EFFECTIVELY changes the "originalState" from what it was before the beforeEach to what it is now.
    a.b.isSpy = false;
    spyOn(a, 'b').andCallFake(function(params) {
        return 'spy2';
    })
    expect(a.b()).toEqual('spy2');
  });

  it('should again return spy1 - but we have overwritten the original state, and can never return to it', function(){
    // inspect the internal tracking of spies:
    expect(this.spies_.length).toBe(1);
    expect(this.spies_[0].originalValue).toBe(originalValue); // FAILS!

    expect(a.b()).toEqual('spy1');
  });
});

// If you were hoping jasmine would cleanup your mess even after the spec is completed...
console.log(a.b == originalValue) // FALSE as you've already altered the global object!
FilmJ
  • 2,011
  • 3
  • 19
  • 27
  • I was trying to find out what I needed to do to stop spying on an object. Your answer is been very clear and helpful. And jasmine rocks for automatically taking care of it doing tear down. – xverges Oct 15 '13 at 05:27
11

In Jasmine 2, the spy state is held in a SpyStrategy instance. You can get hold of this instance calling $.ajax.and. See the Jasmine source code on GitHub.

So, to set a different fake method, do this:

$.ajax.and.callFake(function() { ... });

To reset to the original method, do this:

$.ajax.and.callThrough();
titusd
  • 516
  • 6
  • 5
  • 2
    I don't think `$.ajax.and.andCallThrough();` is correct. Should be `$.ajax.and.callThrough();` – kriskodzi Jan 20 '15 at 12:32
  • That worked for me; inside `beforeEach`: `spyOn(Foobar, 'getFoo').and.returnValue('generic'); }` then inside `it`: `Foobar.getFoo.and.returnValue('special')`. Thanks! – jakub.g Apr 28 '17 at 12:50
  • this is the correct answer. i do the same thing. I got the idea from this post: https://github.com/jasmine/jasmine/issues/160. I don't get why this isn't the top answer? – c_breeez Apr 03 '19 at 15:56
8

From jasmine 2.5, you can use this global setting to update a spy within your test cases:

jasmine.getEnv().allowRespy(true);
Hamzeen Hameem
  • 2,360
  • 1
  • 27
  • 28
7

This worked for me in Jasmine 2.5 to allow re-setting of mock ajax.

function spyOnAjax(mockResult) {
    // must set to true to allow multiple calls to spyOn:
    jasmine.getEnv().allowRespy(true);

    spyOn($, 'ajax').and.callFake(function () {
        var deferred = $.Deferred();
        deferred.resolve(mockResult);
        return deferred.promise();
    });
}

Then you can call it multiple times without error. spyOnAjax(mock1); spyOnAjax(mock2);

user7054363
  • 71
  • 1
  • 1
1

Or you can do it

describe('test', function() {
    var a, c;
    c = 'spy1';
    a = {
      b: function(){}
    };

    beforeEach(function() {
        spyOn(a, 'b').and.callFake(function () {
             return c;
        });
    })

    it('should return spy1', function() {
        expect(a.b()).toEqual('spy1');
    })

    it('should return spy2', function() {
        c = 'spy2';
        expect(a.b()).toEqual('spy2');
    })

})

In this case you use the same Spy but just change the var that it will return..

RafaCianci
  • 11
  • 1
1

I'm posting this answer to address the comment in OP @Tri-Vuong's code - which was my main reason for my visiting this page:

I want to override the spy ... here and do it a little differently

None of the answers so far address this point, so I'll post what I've learned and summarize the other answers as well.

@Alissa called it correctly when she explained why it is a bad idea to set isSpy to false - effectively spying on a spy resulting in the auto-teardown behavior of Jasmine no longer functioning as intended. Her solution (placed within the OP context and updated for Jasmine 2+) was as follows:

beforeEach(() => {
  var spyObj = spyOn(obj,'methodName').and.callFake(function(params){
  }) // @Alissa's solution part a - store the spy in a variable
})

it("should do the declared spy behavior", () => {
  // Act and assert as desired
})

it("should do what it used to do", () => {
  spyObj.and.callThrough(); // @Alissa's solution part b - restore spy behavior to original function behavior
  // Act and assert as desired
})

it("should do something a little differently", () => {
  spyObj.and.returnValue('NewValue'); // added solution to change spy behavior
  // Act and assert as desired
})

The last it test demonstrates how one could change the behavior of an existing spy to something else besides original behavior: "and-declare" the new behavior on the spyObj previously stored in the variable in the beforeEach(). The first test illustrates my use case for doing this - I wanted a spy to behave a certain way for most of the tests, but then change it for a few tests later.

For earlier versions of Jasmine, change the appropriate calls to .andCallFake(, .andCallThrough(), and .andReturnValue( respectively.

LHM
  • 721
  • 12
  • 31
-3

just set the spy method to null

mockedService.spiedMethod = null;
-3

I'm not sure if its a good idea but you can simply set the isSpy flag on the function to false:

describe('test', function() {
    var a = {b: function() {
    }};
    beforeEach(function() {
        spyOn(a, 'b').andCallFake(function(params) {
            return 'spy1';
        })
    })
    it('should return spy1', function() {
        expect(a.b()).toEqual('spy1');
    })

    it('should return spy2', function() {
        a.b.isSpy = false;
        spyOn(a, 'b').andCallFake(function(params) {
            return 'spy2';
        })
        expect(a.b()).toEqual('spy2');
    })

})

But maybe its a better idea to create a new suite for this case where you need an other behavior from your spy.

Andreas Köberle
  • 106,652
  • 57
  • 273
  • 297