How do we clear spy programmatically in Jasmine?

87,715

Solution 1

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.

Solution 2

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)

Solution 3

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

spyOn($, 'ajax');

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

expect($.ajax).toHaveBeenCalled();

$.ajax.calls.reset()

expect($.ajax).not.toHaveBeenCalled();

Solution 4

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!

Solution 5

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();
Share:
87,715

Related videos on Youtube

trivektor
Author by

trivektor

Updated on August 30, 2020

Comments

  • trivektor
    trivektor almost 4 years

    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
    trivektor over 12 years
    Perfect Andreas. Just what I need. If you later on figure out whether it's a good idea or not. Please let me know. Thanks. +1
  • FilmJ
    FilmJ about 11 years
    So all this does is reset the tracking state, if you're looking to restore default behavior this is not going to help.
  • FilmJ
    FilmJ about 11 years
    this is not a great idea, see my answer.
  • xverges
    xverges over 10 years
    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.
  • Leon Fedotov
    Leon Fedotov about 10 years
    this is really not a good idea, this way after the spec will finish running the method will remain a spy instead of the original method see answer by Alissa
  • Matthieu Riegler
    Matthieu Riegler almost 10 years
    This is imho the best solution.
  • Matstar
    Matstar almost 10 years
    Note that this has changed to mySpy.calls.reset() in Jasmine 2.
  • KSev
    KSev almost 10 years
    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.
  • Boris Charpentier
    Boris Charpentier over 9 years
    note that in jasmine 2 it's spyObj.and.callThrough();
  • kriskodzi
    kriskodzi over 9 years
    I don't think $.ajax.and.andCallThrough(); is correct. Should be $.ajax.and.callThrough();
  • Ilker Cat
    Ilker Cat over 8 years
    Hint: In the current version of Jasmine you need to use "and.callFake(...)" instead of "andCallFake(...)"
  • Ilker Cat
    Ilker Cat over 8 years
    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');"
  • marksyzm
    marksyzm over 7 years
    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). jasmine.github.io/2.0/introduction.html#section-Spies
  • jakub.g
    jakub.g about 7 years
    That worked for me; inside beforeEach: spyOn(Foobar, 'getFoo').and.returnValue('generic'); } then inside it: Foobar.getFoo.and.returnValue('special'). Thanks!
  • Watchmaker
    Watchmaker almost 7 years
    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();
  • Chris Knight
    Chris Knight almost 7 years
    This should NOT be the accepted answer! Please see @Alissa's answer
  • Spiral Out
    Spiral Out about 6 years
    mySpy.calls.reset() resets the counter of times the spy had been called. You can check it with expect(spy).toHaveBeenCalledTimes(1)
  • c_breeez
    c_breeez over 5 years
    this is the correct answer. i do the same thing. I got the idea from this post: github.com/jasmine/jasmine/issues/160. I don't get why this isn't the top answer?
  • Thor84no
    Thor84no over 4 years
    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.
  • LHM
    LHM over 4 years
    @Alissa, can you say more about "removeAllSpies()"? I am not sure how it should be implemented.