jasmine.createSpyObj z właściwościami

Oct 28 2020

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

5 jonrsharpe Oct 28 2020 at 01:29

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

1 IanGrainger Nov 11 2020 at 17:57

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