Custom Development

A Beginner's Guide to Angular Unit Tests

Steve Ayers

When I was first learning how to write Angular unit tests, I sat with a coworker and watched him create a simple suite. As he wrote each line of code, I found myself struggling to maintain consciousness. Sure, he was a great teacher, explaining to me what each line meant, but I couldn’t help but be completely overwhelmed with what I was watching. There were so many things to remember and to do and to set up. And that was BEFORE even writing your first test. Spies, injections, and oh God the dollar signs. It looked like jQuery threw up everywhere.

But, I’m stubborn. I wasn’t going to let a few underscores and all these various libraries stand in my way. So, I persevered. The problem was, a lot of my questions were so basic that I was having trouble finding answers to my questions. Only through trial and error and sheepishly asking my coworker how to do things did I start to turn the corner.

So, as my gift to the world, I am offering up some of the things that tripped me up in the beginning when writing Angular unit tests. Most of these are super basic and if you are a member of the Angulari elite, then this is not for you. My hope is that some of these will help the beginner out there because I sure could’ve used them when I was starting out.

Injections Gone Wild

The first time I saw a fully fleshed out Angular unit test, there were more injections than an MLB clubhouse. I was under the impression that Angular had dependency injection built-in and further, that there was one way to inject dependencies. For example, I have seen all of these in tests in various places (StackOverflow, blogs, etc.):

<code>beforeEach(inject(function (_$state_) {
     $state = _$state_;
}));</code>
<code>beforeEach(mocks.inject(function (_$state_) {
     $state = _$state_;
}));</code>
<code>beforeEach(inject(function ($injector) {
     $state = $injector.get('$state');
}));</code>

So, what’s the difference here? Well, my friends. Essentially nothing. The important thing to remember in Angular is there are many, many ways of accomplishing a goal, but there’s only a choice few ways to do it in the most efficient way possible. So in the examples above, all 3 are illustrating a way to inject a $state provider mock and save it to a variable before each test.

The first two examples are pretty much the same except for one is calling a global inject function and the other is calling one on the Angular Mocks module. So, what’s the difference? Again, the answer is nothing. The mocks module actually adds the inject function to the global scope for convenience, so in the examples above the first two are exactly the same.

The third example is basically taking the long way to get to the end result. What it is doing is injecting the Angular $injector service, which is a singleton that Angular uses to back its dependency injection functionality. Each time you inject a dependency into your code, you are witnessing the $injector provider at work. The problem is, in the third example above, you’re injecting the $injector provider then asking that to inject (or ‘get’) the $state provider instead of just relying on the Angular Mocks module to do the injection for you. The $injector service has other responsibilities such as annotations and dynamically injecting your own dependencies, but for the test listed above it is most certainly not needed and just plain overkill.

So, forgetting the third example then, which of the first two is the better way to go? Personally, I prefer the second example because its much clearer (especially to beginners) to understand where inject is being invoked. It does require you to explicitly inject the mocks module into your tests, but that is a practice I’ve been doing for most of my tests anyway.

What’s With the Underscores?

In a lot of unit tests (such as the above example), you’ll see something similar to this:

<code>beforeEach(mocks.inject(function (_$state_) {
     $state = _$state_;
}));</code>

and go crazy trying to figure out where _$state_ is defined and how Angular knows where to find it. But, what’s really happening here is the Angular Mocks module is stripping out the underscores in the beginning and the end of the injected dependency. This allows you to set the injected value into a local variable that is named for the exact name as the dependency you’re working with. In other words, in the above example, this allows me to have a local variable called ‘$state’ (and not ‘state’ or ‘myState’), which is much clearer and easier to understand.

Note: this only works with underscores at the beginning AND the end of the injected dependency. It also will only work in unit tests because the Angular Mocks module is the one that is actually stripping out the underscores.

Unknown Provider

There are many times you’ll see an error similar to this when writing your Angular unit tests in the beginning:

Error: [$injector:unpr] Unknown provider: $stateProvider

Which is insanely confusing, because you were never aware of a '$stateProvider' object. Is this something you need to associate to your dependency? Is this another injection you need to set up? It can get a bit maddening.

One important thing to remember that will ease your initial befuddlement is that Angular is appending the word ‘Provider’ to the end of the dependency you’re trying to inject. Test it out. Throw this in some random part of your unit test:

<code>beforeEach(mocks.inject(function ($stateProvider) {
     state = $stateProvider;
}));</code>

The error you’ll get is:

Error: [$injector:unpr] Unknown provider: $stateProviderProvider

What this error typically means is that the service or factory you are trying to inject belongs to a module that you haven’t yet loaded, or that it doesn't exist. One of the benefits behind Angular modules is that they enable you to only load the code that you are trying to test, so you may be trying to use a module that hasn’t yet been included into your running environment. But, an important concept to remember though is that you probably shouldn’t be injecting the actual service or factory anyway and should be injecting a mock instead.

Creating Mocks

Let’s assume you have the following service:

<code>function MyService() {

     this.currentItem;

     this.initalizeCurrentItem();
}

MyService.prototype.initializeCurrentItem = function () {
     this.currentItem = 0;
}</code>

And now you want to create the mocked version of this service. So, what’s the first step? Lets create a variable ‘MyServiceMock’ and assign an object to it:

var MyServiceMock = {};

OK, now we need a property and a function inside:

<code>var MyServiceMock = {
   currentItem : undefined,
   initializeCurrentItem : function ()….</code>

Hold up. Since I’m mocking this service, do I really need to re-implement the initializeCurrentItem function? What if all you want to test is that initializeCurrentItem was called? What you could do is just define a dummy function such as:

<code>var MyServiceMock = {
   currentItem : undefined,
   initializeCurrentItem : function () {
        return true;
   }
};</code>

Ehhhh, that’s OK I guess if you have no other options, but it has a noticeable code odor. If you’re using any sort of testing framework worth its salt, you should be able to simply create a spy for that function. Here is an example using Jasmine 2.0.

<code>var MyServiceMock = {
   currentItem : undefined,
   initializeCurrentItem : jasmine.createSpy(“initializeCurrentItem”);
};</code>

This will kill two birds with one stone. It will eliminate the need to create a dummy function with nonsense logic inside AND it will create a spy for you as a bonus allowing you to spy on the function and verify it was called. So your tests can look like this:

<code>it(‘should call initializeCurrentItem when the service is created’, function () {
      expect(sut.initializeCurrentItem).toHaveBeenCalled();
}</code>

Jasmine also has the ability to create spies for numerous properties all at once, by specifying the name of the object and then an array of strings which indicate the names of the spies to create. For example, lets suppose our service above had five methods:

initializeCurrentItem()
incrementCurrentItem()
decrementCurrentItem()
setCurrentItem(val)
getCurrentItem()

Rather than having five different createSpy statements, you can do it all at once:

<code>var MyServiceMock = jasmine.createSpyObj(‘MyServiceMock’, [
	‘initalizeCurrentItem’,
	‘incrementCurrentItem’,
	‘decrementCurrentItem’,
	‘setCurrentItem’,
	‘getCurrentItem’
];</code>

Injecting Mocks

Once I figured out how to actually create a mock, I then had to figure out how to get it into my system under test. Angular's dependency injection takes care of grabbing the actual object you need during the running of your application, but during tests, your dependencies should actually be mocked objects. This helps with isolating your test and ensuring it doesn't rely on any external factors. So, how is this possible?

Luckily, Angular supplies a handy approach that allows you to register your mock objects with the $injector: the $provide service. Any mocks you pass to the $provide service will then be injected to your tests at runtime. For example, here is a standard block of code for defining a mock and then registering that mock using $provide.value:

<code>var MainServiceMock;
beforeEach(function () {
     MainServiceMock = {
          currentItem : “123”,
          initializeCurrentItem : jasmine.createSpy(“MyService initializeCurrentItem”)
     };
     $provide.value('MainService', MainServiceMock);
});</code>

Suppose then you have a controller that has two dependencies: the $scope and your MainServiceMock. With the above code, you can simply have the following:

<code>beforeEach(mocks.inject(function ($controller, _$rootScope_) {
     sut = $controller('MainCtrl', {
          $scope : _$rootScope_
     });
}));</code>

Notice that it wasn't necessary to explicitly inject the MainService. The $provide.value call registered our mock with the $injector and it was handled for us. But why, did I need to still inject the scope? Yes, I know, this is something that greatly confused me at first, also.

This is because you need to control which $scope is injected with your controller as this dependency can greatly affect the results of your test. So, think of the $scope object as the one exception to this rule. Instead, to get your $scope, you inject the $rootScope and use that in your tests. This scope is then injected into your controller. Note, you can also invoke $rootScope.$new() to create a new inherited scope with each test run if using the $rootScope will not work for you.

A few notes on the above:

  • As I mentioned before, there are many ways to accomplish something in Angular and creating mocks is no exception. It is theoretically possible to create them by simply creating your own object manually in your beforeEach, assigning it to a local variable, and then explicitly injecting that object. But compared to the $provide.value approach, that would make no sense. You would be doing more work than necessary, since using $provide.value handles all the injection for you. Plus, consistency is king and using the $provide.value approach for injecting all of your mocks, regardless of type of object (service, factory, controller) etc. is definitely a best practice to follow.
  • You cannot use the $provide.value paradigm above after you have used mocks.inject when setting up your tests. This only stands to reason because you are trying to create mock values after you’ve injected them. But, the catch here is you cannot use $provide.value after ANY mocks.inject statements, even if your mocks.inject statement involves an unrelated module. Doing so will result in a pretty clear error from Angular:Error: Injector already created, can not register a module!

That is a few quirks and nuances that I struggled with when I was learning to write unit tests. There is a wealth of information out there on Angular, but sometimes it’s hard to find definitive answers for the minutiae. The comprehension of writing good unit tests is crucial if you’re using Angular because the entire framework was designed with testing in mind. So, it stands to reason that not only should you be learning Angular itself, but you should be learning the testing aspect with equal effort. Learning how to write your tests in a clean and effective manner will make your code that much easier to understand. Especially to the next beginner that comes along.

Steve Ayers
ABOUT THE AUTHOR

Summa Alumni