jasmine.createSpyObj со свойствами

Oct 28 2020

Когда я высмеиваю зависимости в моих тестах Angular, я обычно создаю объект-шпион, используя jasmine.createSpyObj:

const serviceSpy= jasmine.createSpyObj('MyService', ['method']);

затем предоставьте его TestBed:

  providers: [
    {provide: MyService, useValue: serviceSpy}
  ]

Когда я использую его в своем тесте, я могу указать желаемое возвращаемое значение:

serviceSpy.method.and.returnValue(of([...]));

Теперь мне также нужно имитировать свойства, и я не могу понять, как это сделать. createSpyObjпозволяет определять имена свойств:

const serviceSpy= jasmine.createSpyObj('MyService', ['method'], ['property']);

но я безуспешно пробовал различные решения, основанные на многочисленных статьях и ответах, например:

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

Единственный способ сделать это "наполовину" - это:

let fakeValue = true;
const serviceSpy= jasmine.createSpyObj('MyService', ['method'], {'property': fakeValue});

Проблема здесь в том, что это одноразовый набор при создании. Если я хочу изменить ожидаемое значение в тесте, это не сработает.

fakeValue = false;
serviceSpy.property ==> stays to the initial value 'true';

Существует ли решение как для имитационных методов, так и для свойств путем создания шпионского объекта, или я должен создать свой собственный поддельный класс, на котором я затем могу использовать spyOnи spyOnProperty?

Я также хотел бы знать, как используется массив свойств в createSpyObjопределении. Пока я не видел в сети примеров, объясняющих это.

Ответы

5 jonrsharpe Oct 28 2020 at 01:29

В документации (выделено мной):

Вы можете быстро создать объект-шпион с несколькими свойствами, передав массив или хэш свойств в качестве третьего аргумента createSpyObj. В этом случае у вас не будет ссылки на созданных шпионов, поэтому, если вам понадобится изменить их шпионские стратегии позже, вам придется использовать этот Object.getOwnPropertyDescriptorподход .

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

Подсмотрел свойства дескрипторы (смотри , например , Object.definePropertyна MDN ), поэтому доступ шпион объекты , которые вы должны получить объект дескриптора затем взаимодействуют с getи setметодами , определенными на нем.


В TypeScript компилятору нужна небольшая помощь. createSpyObjвозвращает либо anyили SpyObj<T>, а SpyObjтолько определяет методы , за которыми ведется слежка:

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

Итак, чтобы получить доступ .andк геттеру дескриптора, вам понадобится дополнительная цепочка (которая Object.getOwnPropertyDescriptorможет возвращать undefined) и утверждение типа для Spy:

(Object.getOwnPropertyDescriptor(obj, "x")?.get as Spy<() => number>).and.returnValue(7);

Игровая площадка

1 IanGrainger Nov 11 2020 at 17:57

Большое спасибо @jonrsharpe! Я просто добавил функцию, поэтому могу:

spyPropertyGetter(spy, 'propName').and.returnValue(...)

Что определяется как:

function spyPropertyGetter(
  spyObj: jasmine.SpyObj<any>,
  propName: string
) {
  return Object.getOwnPropertyDescriptor(spyObj, propName).get as jasmine.Spy<
    jasmine.Func
  >;
}