jasmine.createSpyObj 具有属性

jasmine.createSpyObj with properties

在我的 Angular 测试中模拟依赖项时,我通常使用 jasmine.createSpyObj:

创建一个间谍对象
const serviceSpy= jasmine.createSpyObj('MyService', ['method']);

然后将其提供给 TestBed:

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

当我在测试中使用它时,我可以指定所需的 return 值:

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;  

我能让它 'half' 工作的唯一方法是:

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

这里的问题是它在创建时是一次性的。如果我想在测试中更改期望值,它不起作用。

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

是否存在通过创建间谍对象来解决模拟方法和属性的解决方案,或者我是否应该创建自己的假 class 然后在其上使用 spyOnspyOnProperty?

我也想知道createSpyObj定义中的properties数组有什么用。到目前为止,我还没有在网上看到任何解释它的例子。

根据 the documentation(强调我的):

You can create a spy object with several properties on it quickly by passing an array or hash of properties as a third argument to createSpyObj. In this case you won’t have a reference to the created spies, so if you need to change their spy strategies later, you will have to use the Object.getOwnPropertyDescriptor approach.

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 on MDN),因此要访问间谍对象,您需要获取描述符对象然后与 getset 定义的方法。


在 TypeScript 中,编译器需要一点帮助。 createSpyObj returns anySpyObj<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
};

因此,要访问描述符 getter 上的 .and,您需要 optional chaining (as Object.getOwnPropertyDescriptor may return undefined) and a type assertion to a Spy:

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

Playground

非常感谢@jonrsharpe!我刚刚添加了一个函数,所以我可以这样做:

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

定义为:

function spyPropertyGetter<T, K extends keyof T>(
  spyObj: jasmine.SpyObj<T>,
  propName: K
): jasmine.Spy<() => T[K]> {
  return Object.getOwnPropertyDescriptor(spyObj, propName)?.get as jasmine.Spy<() => T[K]>;
}

这个也能完成这项工作:)

const serviceSpy= jasmine.createSpyObj('MyService', ['method']);
Object.getOwnPropertyDescriptor(serviceSpy, "method").value.and.returnValue("test");

感谢 post,非常有帮助。

我添加了一个 Stackblitz Demo (Angular v12) 到 [=v15=] 乔恩·夏普的 & s.alems 解决方案