'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.



Solution 1:[1]

Thank you so much @jonrsharpe! I just added a function so I could do:

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

Which is defined as:

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

Solution 2:[2]

This one will also do the job :)

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

Solution 3:[3]

Thanks for the post, was super helpfull.

I added a Stackblitz Demo (Angular v12) to
jonrsharpe's & s.alems solution

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 s.alem
Solution 2 Peter Csala
Solution 3 Florian Uhlmann