Type-safe Jasmine Spies
TypeScript for all its glory is pretty handy when it comes to better Intellisense and type safety. Yet one of those situations that constantly drove me crazy was writing Jasmine tests. Specifically talking about Jasmine Spies.
The test candidate
So imagine we have this little app with a small Service
fetching a server-side API and a MyComponent
, consuming the services via constructor injection.
1 | class Service { |
Calling the method doStuff
should fetch the data, check whether the property msg
is success
and return true
in that case.
Creating the test
A simple describe
and it
should be enough to depict what we’re trying to achieve
1 | describe("My very special scenario", () => { |
Running your test will now successfully pass the test but there is this one ugly squiggle line at the .and.
which results in an ugly typescript error:
1 | [ts] Property 'and' does not exist on type '() => Promise<any>'. |
Thats understandable since our mocked service actually doesn’t provide a the getServerData
method as spy. And most of the Stackoverflow questions and blog posts so far recommend an ugly cast like this:
1 | (mockedService.getServerData as jasmine.Spy) |
Meeeh if you ask me
The fix
Now what if I told you the solution would be just to initially cast the mockedService
as a Spied<Service>
vs Service
:
1 | it("should support spies the nice type safe way", async () => { |
Reads much nicer doesn’t it? Here’s the code that makes that possible:
1 | export type Spied<T> = { |
The generic type Spied
, a mapped type, takes any other type, and extends all of its methods using the x in keyof T
syntax and mapping it to a jasmine.Spy
. This means now every method not only acts as a function but also as an extended Spy object.
And the red squiggles are gone !!!
Conclusion
With this simple trick now you can save tons of strict casts throughout your test code and again enjoy the type-safe life, the easy way. Man all of this wouldn’t even be needed in VanillaJS. Just sayin … :)
photo credit: peterbwiberg: Spy camera via Pixabay (license)