jasmine.createSpyObj 与属性

Ron*_*ald 9 javascript jasmine

在我的 Angular 测试中模拟依赖项时,我通常使用jasmine.createSpyObj以下命令创建一个间谍对象:

const serviceSpy= jasmine.createSpyObj('MyService', ['method']);
Run Code Online (Sandbox Code Playgroud)

然后将其提供给 TestBed:

  providers: [
    {provide: MyService, useValue: serviceSpy}
  ]
Run Code Online (Sandbox Code Playgroud)

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

serviceSpy.method.and.returnValue(of([...]));
Run Code Online (Sandbox Code Playgroud)

现在我还需要模拟属性,但我不知道应该如何完成。createSpyObj确实允许定义属性名称:

const serviceSpy= jasmine.createSpyObj('MyService', ['method'], ['property']);
Run Code Online (Sandbox Code Playgroud)

但我已经尝试了基于大量文章和答案的各种解决方案,但没有任何成功,例如:

// 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;  
Run Code Online (Sandbox Code Playgroud)

我可以让它“一半”工作的唯一方法是:

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

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

fakeValue = false;
serviceSpy.property ==> stays to the initial value 'true';
Run Code Online (Sandbox Code Playgroud)

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

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

jon*_*rpe 23

根据文档(强调我的):

您可以通过将一个数组或属性散列作为第三个参数传递给 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);
});
Run Code Online (Sandbox Code Playgroud)

Spied 属性是描述符(参见例如Object.definePropertyMDN),因此要访问 spy 对象,您需要获取描述符对象,然后与定义在其上的getset方法进行交互。


在 TypeScript 中,编译器需要一点帮助。createSpyObj返回anyor SpyObj<T>,并且 aSpyObj仅将方法定义为被监视:

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
};
Run Code Online (Sandbox Code Playgroud)

因此,要访问.and的描述符的getter,你需要可选的链接(如Object.getOwnPropertyDescriptor可能返回undefined)和一个类型断言Spy

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

操场

  • 我认为已经很接近了。但这在打字稿(Angular)中不起作用。“and”是间谍对象的属性。`类型 '() =&gt; any' 上不存在属性 'and'`。 (6认同)
  • @Ronald它在JS中工作,它告诉你这是一个类型问题而不是功能问题。我添加了一种方法来解决这个问题。 (2认同)

Ian*_*ger 7

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

spyPropertyGetter(spy, 'propName').and.returnValue(...)
Run Code Online (Sandbox Code Playgroud)

其中定义为:

spyPropertyGetter(spy, 'propName').and.returnValue(...)
Run Code Online (Sandbox Code Playgroud)

  • 我可能倾向于在这里使用泛型,因此 `returnValue` 可以是类型安全的: function `function spyPropertyGetter&lt;T, K extends keyof T&gt;(spyObj: SpyObj&lt;T&gt;, propName: K): Spy&lt;() = &gt; T[K]&gt; { /* ... */ }`. (3认同)