jasmine.createSpyObj z właściwościami
Podczas mockowania zależności w moich testach Angular zwykle tworzę obiekt szpiegowski za pomocą jasmine.createSpyObj:
const serviceSpy= jasmine.createSpyObj('MyService', ['method']);
następnie przekaż go do TestBed:
providers: [
{provide: MyService, useValue: serviceSpy}
]
Kiedy używam go w moim teście, mogę określić żądaną wartość zwracaną:
serviceSpy.method.and.returnValue(of([...]));
Teraz muszę też kpić z nieruchomości i nie mogę się dowiedzieć, jak to zrobić. createSpyObjpozwala na definicję nazw właściwości:
const serviceSpy= jasmine.createSpyObj('MyService', ['method'], ['property']);
ale wypróbowałem różne rozwiązania w oparciu o liczne artykuły i odpowiedzi bez powodzenia, np:
// Cannot read property 'and' of undefined
serviceSpy.property.and.returnValue(true);
// not declared configurable
spyOnProperty(serviceSpy, 'property').and.returnValue(true);
// no build errors, but value stays 'undefined'
serviceSpy.property = true;
Jedyny sposób, w jaki mógłbym sprawić, by to działało „w połowie”, to:
let fakeValue = true;
const serviceSpy= jasmine.createSpyObj('MyService', ['method'], {'property': fakeValue});
Problem polega na tym, że jest to jednorazowa akcja podczas tworzenia. Jeśli chcę zmienić oczekiwaną wartość w teście, to nie działa.
fakeValue = false;
serviceSpy.property ==> stays to the initial value 'true';
Czy istnieje rozwiązanie zarówno dla fałszywych metod, jak i właściwości poprzez utworzenie obiektu szpiegowskiego, czy też powinienem utworzyć własną fałszywą klasę, na której mogę następnie używać spyOni spyOnProperty?
Chciałbym również wiedzieć, jakie jest użycie tablicy properties w createSpyObjdefinicji. Jak dotąd nie widziałem w sieci żadnego przykładu, który to wyjaśnia.
Odpowiedzi
Zgodnie z dokumentacją (wyróżnienie moje):
Możesz szybko utworzyć obiekt szpiegowski z kilkoma właściwościami, przekazując tablicę lub skrót właściwości jako trzeci argument do
createSpyObj. W tym przypadku nie będziesz mieć odniesienia do stworzonych szpiegów, więc jeśli będziesz musiał później zmienić ich strategie szpiegowskie, będziesz musiał zastosowaćObject.getOwnPropertyDescriptorpodejście .it("creates a spy object with properties", function() { let obj = createSpyObj("myObject", {}, { x: 3, y: 4 }); expect(obj.x).toEqual(3); Object.getOwnPropertyDescriptor(obj, "x").get.and.returnValue(7); expect(obj.x).toEqual(7); });
Właściwości szpiegowskie są deskryptorami (patrz np. W Object.definePropertyMDN ), więc aby uzyskać dostęp do obiektów szpiegowskich, musisz pobrać obiekt deskryptora, a następnie wejść w interakcję z metodami geti setzdefiniowanymi w nim.
W języku TypeScript kompilator potrzebuje trochę pomocy. createSpyObjzwraca albo anyalbo SpyObj<T>, a SpyObjtylko definiuje metody jako szpiegowane:
type SpyObj<T> = T & {
[K in keyof T]: T[K] extends Func ? T[K] & Spy<T[K]> : T[K];
// | if it's a | spy on it | otherwise leave
// | callable | | it alone
};
Aby uzyskać dostęp .anddo metody pobierającej deskryptor, będziesz potrzebować opcjonalnego łączenia łańcuchowego (co Object.getOwnPropertyDescriptormoże zwrócić undefined) i potwierdzenia typu do Spy:
(Object.getOwnPropertyDescriptor(obj, "x")?.get as Spy<() => number>).and.returnValue(7);
Plac zabaw
Dziękuję bardzo @jonrsharpe! Właśnie dodałem funkcję, więc mogłem zrobić:
spyPropertyGetter(spy, 'propName').and.returnValue(...)
Który jest zdefiniowany jako:
function spyPropertyGetter(
spyObj: jasmine.SpyObj<any>,
propName: string
) {
return Object.getOwnPropertyDescriptor(spyObj, propName).get as jasmine.Spy<
jasmine.Func
>;
}