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.
In Jasmine, you can do anything with a property spy that you can do with a function spy, but you may need to use different syntax. Use spyOnProperty to create either a getter or setter spy. it("allows you to create spies for either type", function() { spyOnProperty(someObject, "myValue", "get").
callFake(fn)Tell the spy to call a fake implementation when invoked.
SpyOn is a Jasmine feature that allows dynamically intercepting the calls to a function and change its result. This example shows how spyOn works, even if we are still mocking up our service.
Using Jasmine spies to mock codeYou set the object and function you want to spy on, and that code won't be executed. In the code below, we have a MyApp module with a flag property and a setFlag() function exposed. We also have an instance of that module called myApp in the test. To spy on the myApp.
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 theObject.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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With