Using Jasmine Spies to Create Mocks and Simplify the Scope of Your Tests
Jasmine spies are a great and easy way to create mock objects for testing. By using a Spy object, you remove the need to create your own function and class stubs just to satisfy test dependencies.
Some TypeScript Code
Let’s say you have this service for saving a person:
iperson-service.ts
If you were going to test this without mocks, you’d have to create method stubs for your validator and data context then add checks in there to make sure they were called. This is just adding to the complexity of the test and taking you further away from your base code.
createSpyObj
There are a few ways to create mocks with Jasmine. You can
- use spyOn to create a spy around an existing object
- use jasmine.createSpy to create a testable function
- use jasmine.createSpyObj to create an object with a number of internal spy functions
It’s the latter that we’ll be using.
The interface for our validation service looks like this:
iperson-validator.ts
So, to create out mock, we’ll do this:
What’s this doing?
We’re creating a new Spy object with an alias of validator. This object has one Spy function called isValid.
Once this has been created, we can monitor any calls to isValid and control what it returns.
We can create the mock for our data context object in the same way.
idata-context.ts
and
Let’s start setting up our tests.
Testing Save Works For a Valid Person
Here’s our test function. We’ll go through it line by line afterwards.
Setting up the mocks
We create the mocks on lines 7-13:
The two mocks are created as above. We’ll do this in the beforeEach function to make sure that we create clean objects at the start of every test.
We use the any type for the mock objects so that we don’t have issues attaching Jasmine’s and function onto properties.
Inside our test, we use this functionality to set what value we want our service to return.
This will cause any calls to isValid to return true.
For TypeScript, we need to cast the two mocks into their required types when we instantiate our service.
And our validPerson object is just an empty literal.
Now that we have our service and objects set up, we can call the function we want to test.
In our assertions, we can check to make sure the validator method was called using Jasmine’s toHaveBeenCalledWith function, passing in the same IPerson instance we passed to save.
And we can use the same function to make sure savePerson has been called on our data context.
That lets us test that everything has worked as expected. But what about testing for errors?
Testing Exceptions
In our service, we throw an error if the IPerson instance is invalid.
Our test for the error will look like this:
At the start, we’re setting the isValid method to return false this time.
We create then our service as before.
Our test for the exception is a little different though. Jasmine uses the toThrow expectation to test for thrown errors. To use this with expect, we need to wrap it in a containing function like so:
The containing function allows us to separate errors in our Jasmine spec with errors thrown by our test code.
We can then use the toHaveBeenCalledWith method again to check our validation method has been called:
and the not modifier to ensure that the data context’s savePerson method has not been called:
If you want to grab the code used here, it’s available on GitHub.