javascript - globala Jest SpyOn-funktionen anropar inte den ursprungliga funktionen
Jag hoppas att någon kan hjälpa mig att förstå interaktiviteten mellan JS-prototyper och jest.spOn().
Jag har ett litet exempel: En exempelklass i filen TestObj.ts:
export default class TestObj {
foo() {
// Do Something e.g.
console.log("Hello World!");
}
}
Följande exempel på testfall lyckas, men console.log körs aldrig.
import TestObj from './TestObj';
const spyObj = jest.spyOn(TestObj.prototype, 'foo');
test('debug test', () => {
const obj = new TestObj();
obj.foo();
expect(spyObj).toHaveBeenCalled();
});
Om jag ändrar Exempel-Testfallet till följande, lyckas testet och console.log-satsen anropas som förväntat.
import TestObj from './TestObj';
test('debug test', () => {
const spyObj = jest.spyOn(TestObj.prototype, 'foo');
const obj = new TestObj();
obj.foo();
expect(spyObj).toHaveBeenCalled();
});
Någon idé om varför versionen som använder den globala spyOn-variabeln inte fungerar som förväntat?
Redigera :
Det verkar inte vara relaterat till prototyper. Samma problem finns för en funktion utan någon typ av klass, redigerar det första kodsnutten (TestObj.ts) till detta:
export foo() {
// Do Something e.g.
console.log("Hello World!");
};
Vi får samma problem för det uppdaterade andra utdraget. (Testet lyckas men konsolloggen nås aldrig.)
import * as testlib from './TestObj';
const spyObj = jest.spyOn(testlib, 'foo');
test('debug test', () => {
testlib.foo();
expect(spyObj).toHaveBeenCalled();
});
Men om vi uppdaterar den andra kodsnutten till följande lyckas testet och konsolloggen körs:
import * as testlib from './TestObj';
const spyObj: jest.SpyInstance;
beforeEach(() => {
spyObj = jest.spyOn(testlib, 'foo');
});
test('debug test', () => {
testlib.foo();
expect(spyObj).toHaveBeenCalled();
});
Jag har dock fortfarande ingen aning om varför jag upptäcker detta problem.
Svar #1
vem som än kommer över detta inlägg,
Problembeskrivning
Jag gjorde mycket mer forskning (försök och fel, MDN, Jest-manualen och många Medium-artiklar) och jag antar att jag har kommit på orsaken till det märkliga beteendet. För att förstå detta problem är det viktigt att känna till ett antal punkter:
- 1: JS-prototyper är globala variabler som varje objekt av motsvarande typ förlitar sig på.
- 2: Jest återställer inte globala variabler efter varje test, ändringar som gjorts före eller under ett test i en global variabel kommer att behållas för hela testsviten (filen).
- 3: Jest spy on-funktionen är faktiskt en kopia av den angivna funktionen med en implementation som anropar funktionen själv. t.ex.:
jest.SpyOn(TestObj.prototype, 'foo');implementeras faktiskt som:TestObj.prototype.foo=new jest.fn().mockImplementation(() => {original_TestObj.prototype.foo()});Detta innebär att spionera på en funktion i en klassprototyp faktiskt ändrar en global variabel. - 4: Beroende på din jest-konfiguration finns möjligheten att återställa mockup-funktioner före varje test till standardvärdet. Men var försiktig, standardfunktionen för
spyOnverkar vara densamma som förjest.fn()själv, en tom implementation, vilket betyder att mockup fortfarande kan anropas men ingen kod, särskilt inte den ursprungliga implementeringen, körs.
Lösning
- undvik att ändra globala variabler om du vill att dina testfall ska vara oberoende av varandra.
- undvik att spionera på prototyper, i testfall, om du bara behöver spionen i ett enda test, försök att spionera på det lokala objektet t.ex:
test('should foo', () => {
const testObj = new TestObj();
const spyOnFn = jest.spyOn(testObj, 'foo');
// Do anything
expect(spyOnFn).to//Have been anything
});
- om du behöver
spyOn-implementationer av samma funktion i mer än ett test, försök att skapa en global variabel för testet men använd jest:s before each-funktionalitet för att sätta upp Spyen. Denna funktionalitet körs efter återställningen av alla mocks (om det är aktiverat). t.ex.:
let spyOnFunction1: jest.SpyInstance;
beforeEach(()=> {
spyOnFunction1 = jest.spyOn(TestObj.prototype, 'foo');
});
