Wie klären wir Spione programmgesteuert in Jasmine?

77

Wie klären wir den Spion programmgesteuert in einer Jasmin-Testsuite? Vielen Dank.

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
})
Tri Vuong
quelle
4
Sicher, dass Sie die richtige "richtige" Antwort gewählt haben?
Hgoebl

Antworten:

1

Ich bin mir nicht sicher, ob es eine gute Idee ist, aber Sie können einfach das isSpyFlag für die Funktion auf false setzen:

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');
    })

})

Aber vielleicht ist es eine bessere Idee, eine neue Suite für diesen Fall zu erstellen, in dem Sie ein anderes Verhalten von Ihrem Spion benötigen.

Andreas Köberle
quelle
2
Perfekter Andreas. Nur was ich brauche. Wenn Sie später herausfinden, ob es eine gute Idee ist oder nicht. Lass es mich wissen, bitte. Vielen Dank. +1
Tri Vuong
1
Das ist keine gute Idee, siehe meine Antwort.
FilmJ
3
Dies ist wirklich keine gute Idee. Auf diese Weise bleibt die Methode nach Abschluss der Ausführung der Spezifikation ein Spion anstelle der ursprünglichen Methode. Siehe Antwort von Alissa
Leon Fedotov.
Hinweis: In der aktuellen Version von Jasmine müssen Sie "and.callFake (...)" anstelle von "andCallFake (...)" verwenden
Ilker Cat
7
Dies sollte NICHT die akzeptierte Antwort sein! Bitte sehen Sie @ Alissas Antwort
Chris Knight
132

Die Einstellung isSpyauf falseist eine sehr schlechte Idee, da Sie seitdem einen Spion ausspionieren und wenn Jasmine die Spione am Ende Ihrer Spezifikation löscht, erhalten Sie nicht die ursprüngliche Methode. Die Methode entspricht dem ersten Spion.

Wenn Sie bereits eine Methode ausspionieren und stattdessen die ursprüngliche Methode aufgerufen werden soll, sollten Sie aufrufen, andCallThrough()wodurch das erste Spionageverhalten außer Kraft gesetzt wird.

zum Beispiel

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

Sie können alle Spione löschen, indem Sie this.removeAllSpies()( this- spec) aufrufen.

Alissa
quelle
1
Dies ist imho die beste Lösung.
Matthieu Riegler
Ich möchte lieber sagen, dass es schlecht ist, isSpy auf false zu setzen. Ich mag es, wenn Andreas K über den Tellerrand hinaus denkt.
KSev
22
Beachten Sie, dass es in Jasmin 2 spyObj.and.callThrough () ist.
Boris Charpentier
4
Wenn Sie die ursprüngliche Funktion nicht aufrufen möchten, sondern nur den ursprünglichen Spion für eine verschachtelte Beschreibung überschreiben möchten, geben Sie einen anderen Wert mit "spyObj.and.returnValue ('irgendein anderer Wert')" zurück.
Ilker Cat
2
Mit der neuen Syntax: spyOn(obj,'methodName').and.returnValue(true);Stellen Sie den Spion so ein, dass er einen Wert zurückgibt. Dann wollen Sie in einem weiteren Test, dass es die eigentliche Funktion wie obj.methodName.and.callThrough();
folgt
46

Ich denke, dafür ist .reset () gedacht :

spyOn($, 'ajax');

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

expect($.ajax).toHaveBeenCalled();

$.ajax.calls.reset()

expect($.ajax).not.toHaveBeenCalled();
hässlich
quelle
13
Alles, was dies bedeutet, ist das Zurücksetzen des Verfolgungsstatus. Wenn Sie das Standardverhalten wiederherstellen möchten, hilft dies nicht weiter.
FilmJ
18
Beachten Sie, dass dies mySpy.calls.reset()in Jasmine 2 geändert wurde.
Henrik N
1
mySpy.calls.reset()Setzt den Zähler zurück, zu dem der Spion gerufen wurde. Sie können es überprüfen mitexpect(spy).toHaveBeenCalledTimes(1)
Spiral Out
1
Wie hat eine Antwort, die die Frage völlig falsch beantwortet, fast 40 positive Stimmen? In der Frage wird gefragt, wie der Anrufzähler überschrieben andCallFakeund nicht zurückgesetzt werden soll.
Thor84no
20

So werden Spione zwischen den Spezifikationen automatisch zurückgesetzt.

Sie erhalten tatsächlich nicht den Vorteil einer "Wiederherstellung" der ursprünglichen Funktion, wenn Sie sie andCallFake()innerhalb einer verwenden beforeEach()und dann versuchen, sie innerhalb einer Spezifikation zwangsweise zu ändern (was wahrscheinlich der Grund ist, warum versucht wird, Sie daran zu hindern).

Seien Sie also vorsichtig, insbesondere wenn Ihr Spion auf ein globales Objekt wie jQuery eingestellt ist.

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
quelle
Ich habe versucht herauszufinden, was ich tun muss, um ein Objekt nicht mehr auszuspionieren. Ihre Antwort war sehr klar und hilfreich. Und Jasmin rockt, um sich automatisch darum zu kümmern, dass es abreißt.
xverges
9

In Jasmine 2 wird der Spionagestatus in einer SpyStrategy-Instanz gespeichert. Sie können diesen Instanzaufruf erhalten $.ajax.and. Siehe den Jasmine-Quellcode auf GitHub .

Um eine andere gefälschte Methode festzulegen, gehen Sie folgendermaßen vor:

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

Gehen Sie folgendermaßen vor, um zur ursprünglichen Methode zurückzukehren:

$.ajax.and.callThrough();
titusd
quelle
2
Ich denke nicht, dass $.ajax.and.andCallThrough();es richtig ist. Sollte sein$.ajax.and.callThrough();
kriskodzi
Das hat bei mir funktioniert; innen beforeEach: spyOn(Foobar, 'getFoo').and.returnValue('generic'); }dann innen it: Foobar.getFoo.and.returnValue('special'). Vielen Dank!
Jakub.g
Das ist die richtige Antwort. Ich mache dasselbe. Ich habe die Idee aus diesem Beitrag: github.com/jasmine/jasmine/issues/160 . Ich verstehe nicht, warum dies nicht die beste Antwort ist.
c_breeez
7

Dies funktionierte für mich in Jasmine 2.5, um das Zurücksetzen von Mock Ajax zu ermöglichen.

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();
    });
}

Dann können Sie es mehrmals ohne Fehler aufrufen. spyOnAjax (mock1); spyOnAjax (mock2);

user7054363
quelle
2

Ab Jasmin 2.5 können Sie diese globale Einstellung verwenden, um einen Spion in Ihren Testfällen zu aktualisieren:

jasmine.getEnv().allowRespy(true);
Hamzeen Hameem
quelle
1

Oder du kannst es schaffen

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 diesem Fall verwenden Sie denselben Spion, ändern jedoch nur die Variable, die zurückgegeben wird.

RafaCianci
quelle
1

Ich poste diese Antwort, um den Kommentar im Code von OP @ Tri-Vuong zu adressieren - was mein Hauptgrund für meinen Besuch auf dieser Seite war:

Ich möchte den Spion außer Kraft setzen ... hier und es ein wenig anders machen

Keine der bisherigen Antworten befasst sich mit diesem Punkt, daher werde ich das Gelernte veröffentlichen und auch die anderen Antworten zusammenfassen.

@Alissa nannte es richtig , wenn sie erklärt , warum es eine schlechte Idee, Satz ist isSpyzu false- effektiv auf einem Spion Spionage was in der Auto-Teardown Verhalten von Jasmin keine funktionierende länger als gedacht. Ihre Lösung (im OP-Kontext platziert und für Jasmine 2+ aktualisiert) war wie folgt:

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
})

Der letzte itTest zeigt, wie man das Verhalten eines vorhandenen Spions in etwas anderes als das ursprüngliche Verhalten andändern kann : " -declare" das neue Verhalten auf dem spyObj, das zuvor in der Variablen in der gespeichert warbeforeEach() . Der erste Test veranschaulicht meinen Anwendungsfall dafür - ich wollte, dass sich ein Spion für die meisten Tests auf eine bestimmte Weise verhält, aber später für einige Tests ändert.

Für frühere Versionen von Jasmin, ändern Sie die entsprechenden Anrufe .andCallFake(, .andCallThrough()und .andReturnValue(jeweils.

LHM
quelle
-1

Setzen Sie einfach die Spionagemethode auf null

mockedService.spiedMethod = null;
dasAnderl ausMinga
quelle