Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
201 views
in Technique[技术] by (71.8m points)

javascript - jasmine.createSpyObj with properties

When mocking dependencies in my Angular tests, I usually create a spy object using jasmine.createSpyObj:

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

then provide it to the TestBed:

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

When I use it in my test, I can then specify the desired return value:

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

Now I also need to mock properties and I cannot find out how it should be done. createSpyObj does allow the definition of property names:

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

but I've tried varies solutions based on the numerous articles and answers out there without any success, e.g.:

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

The only way I could make it 'half' work is:

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

The problem here is that it's a one-time set at creation. If I want to change the expected value in the test, it does not work.

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

Does there exist a solution to both mock methods and properties by creating a spy object, or should I create my own fake class on which I can then use spyOn and spyOnProperty?

I would also like to know what the usage is of the properties array in the createSpyObj definition. So far I have not seen any example on the web that explains it.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Per the documentation (emphasis mine):

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

Spied properties are descriptors (see e.g. Object.defineProperty on MDN), so to access the spy objects you need to get the descriptor object then interact with the get and set methods defined on it.


In TypeScript, the compiler needs a bit of help. createSpyObj returns either any or SpyObj<T>, and a SpyObj only defines the methods as being spied on:

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

So to access .and on the descriptor's getter, you'll need 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


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...