Please note: This article assumes that this isn’t the first time you’ve seen Angular, but is for a beginner by nature. You should know what Controllers, directives and services are, otherwise you might need to do a little bit of reading first. Ideally you’ll have also covered some basic Angular unit testing before.
We don’t use Jasmine in our unit tests at UVD, we have a nice cocktail of Karma (our test runner), Mocha, Chai and Sinon which you’ll see in the examples below, but if you’re only familiar with Jasmine, it’s very similar so hopefully you’ll be alright.
As we’ve established that you know a little about AngularJS, you’ll have undoubtedly heard one of the main reasons it’s become so popular: it’s just so damn testable. One of the main reasons for this is due to Dependency Injection (DI).
Dependency Injection is a software design pattern that implements inversion of control for software libraries
This definition is a little bit vague, so let’s get back to Angular for an example:
Each line of this object relates to the ‘dependencies’ that are injected into your controller in your actual code. Here’s a small example:
Hopefully you can connect the two examples together. Being able to inject any service into a controller or directive means that you can mock everything and anything. Great, right?!
$provide has a few functions, but I’ll predominantly be focusing on the value and constant functions for my examples. If you want to find out more about the
$provide service before I get into an example you should take a look at the AngularJS docs.
I found the $provide service particularly useful when building Authentication into the Sprinter project that I’ve been working on, below is how $provide helped.
A word of caution: the $provide service should really be used as a last resort but it can get you out of a bind. Generally, you can $provide anything, but shouldn’t. The best use case that I have found is for when you are testing that something that happens when a service is first instantiated. With that in mind, let’s take a look at the code below:
There’s quite a lot going on here and I understand that an AngularJS newbie may be a little overwhelmed by that. To summarise, we’re mocking the cookieStore with cookieStoreMock before each of the tests are run. We’re utilising the $provide in our service unit tests to tell Angular to use our mocked cookieStore, not it’s native $cookieStore service.
Now in this instance, the $cookieStore is used when the service is instantiated. This means that we cannot stub or spy on this function as by the time we’ve stubbed it, the service that we’re testing has already run and used the real $cookieStore. Hence why we must overwrite Angular’s native $cookieStore so that when our service runs, it will be using our stubbed version. Heres a little example:
This means that if we were to mock out the $cookieStore with Sinon, as in the example below, the tests would fail because we’re mocking after our service has already run using the real $cookieStore.
This is where $provide is particularly useful and can really get us out of a bind.
Sinon is a great unit testing library which allows you to spy on functions. When I say spy, I don’t mean like James Bond, I mean we can ‘spy’ on it and see if it’s being called by our code.
Sinon has two main methods, stub and spy, stubs can be either anonymous, or wrap existing functions. When wrapping an existing function with a stub, the original function isn’t called and this enables us to assert that functions are called without them affecting the state of the application. A spy is best used when we don’t want to alter or mock the behaviour of an existing function, we just want to make sure it’s being called. Here’s an example of how we might assert this given that we set the
cookieStoreMock = sinon.stub();
Anyway, let’s not get too bogged down. If there are comments asking for a bit more of an explanation in to what libraries we use for our unit tests then I’ll happily oblige!
To quote many cliché tech blogs and even Angular themselves..
With great power comes great responsibility
The key to this really is thinking about when and how you should use $provide. I myself have fallen into the trap of ‘hey this is awesome let’s just $provide everything and we can test our code thoroughly’.. and it’ll will work! It just gets a bit unnecessary and becomes bad practice. For example:
In the majority of cases you’ll be able to stub or spy on what you need to without using $provide, and this is where sinon really comes into its own. It’s generally not a good idea to $provide a service unless its absolutely necessary. For those that are not too sure how to go about stubbing something with Sinon, I’ve provided an example:
Another thing to note here is that if you’re thinking of using this in a controller, there’s probably a better way of doing it. It’s generally a bit trickier to mock and inject into a directive or service, but it’s still possible. If you hit a bit of a wall, $provide can get you going again.
Thats a wrap
We’ve highlighted how testable AngularJS is and how to use $provide for the right problems. I know in this instance I’ve used it to mock the $cookieStore service but you could use it to mock any Angular service, or indeed any service that you see fit. Just remember, only use it where you need to. As Ryan would say:
Take the path of least resistance
Well thats all from my first UVD blog post, be sure to ridicule/praise what I have written in the comments, or maybe let me know if you feel that anything else could do with explaining.