Tuesday, May 21, 2013

AngularJS - Unit Testing - Controllers

Updated Feb 6, 2014 -  I'm changing this around a lot. A lot as changed since I wrote the original article and a lot has stayed the same. Most notably how Angular 1.2 handles promises on the $scope when processing the view, and some changes around testing promises. Since I see this gets some traffic and I hate the idea that I'm showing people the wrong thing, I'm going to try to keep this updated as time goes on. (Angular 2.0 will probably be a whold new post though)

Since Controllers carry the "business logic" of your Angular application, they're probably the single most important thing to unit test in your Application. I've run across a few tutorials on this subject, but most of them cover only the simplest scenarios. I'm going to try to add some slightly more complicated stuff in to my controller and test it, just to show examples. As I think of new examples as time goes on, I'll try to add those too.

For Unit Testing Services and Directives see these other Posts:

The Controller: What are you testing?

First off, what is a controller? A controller is an instance of an object defined by executing the controller function as a class constructor. If you're new to JavaScript, that means it's calling the function, but in the context of creating an object. All of this aside, most of what a controller is doing is setting up your $scope object with properties and functions you can use to wire it to a view. This will be the lion's share of what you're testing.

Recommended Testing Suite: Jasmine

The recommended tool for testing Angular is Jasmine. You can, of course, use any unit testing tool you like, but for this blog entry, we'll be using Jasmine. To get started with Jasmine they have some really well annotated code on their site as a tutorial of sorts, but I'd recommend just going to Plunker and starting a new "Angular + Jasmine" Plunk and fiddling around until you get the hang of it.

To TDD or not to TDD? Yes.

I'm not going to go into the specifics of TDD, whether or not you should use it, the pros and cons of TDD, or even attack this blog entry from that angle. I'm going to assume that if you're here, you've probably written some Angular controller, or you know how to write an Angular controller, and you're thinking "how do I test this thing?". So we'll just cover some of those basics, mkay?

Recommended for Later: Karma or Grunt automated tests

Generally, I'd recommend using something like Karma or grunt-contrib-jasmine to run your unit tests automatically in Node... But that's probably another lesson for another day. For now, let's just learn some Jasmine (1.3.X) basics.

Right Now: Jasmine Basics

Let's start off with the basic Jasmine Set up. This is what's required to run the Jasmine specs you're going to write, and produce a report in HTML format you can read. So to do all of this, you will create some HTML file, we'll call it "index.html" for now. and this would be the basic content of it:

An Example Controller

So now we'll need something to test. I'm going to make up a completely contrived controller to create some unit testing examples against. Nothing special, and nothing that might even make sense. It's just different things you might commonly do in an Angular controller, that you might need to test. In the specsRunner.html file above, this would be our "app.js".

Unit Tests For Our Example Controller

So, given the above controller, here is a battery of unit tests that tests the behavior of this controller. Well, more importantly, it tests what has been set up on the $scope by the controller function. In the specsRunner html (above), this would be in our "specs.js":

The Simple Tests

I don't want to dwell too much on the first two tests. They're fairly straight forward, and I don't want to patronize anyone that's made it this far. They're your basic, basic, unit tests. Make a call, assert a value, the end.

Testing a $watch()

Okay, here there's a little trick. If you have a $watch set up on a property, or on anything really, and you want to test it, all you need to do is update whatever you're watching on the $scope (or wherever it is), then call $scope.$apply(). Calling $apply will force a digest which will process all of your $watches.

Testing Service Calls

Testing services calls is easy: Mock the service, spy on it's methods, use expect(service.method).toHaveBeenCalled() or expect(service.method).toHaveBeenCalledWith(arg1, arg2) to verify it's been called. Pretty simple.

...and Asynchronous Service Calls

Testing async calls with Jasmine in Angular gets a little different than it might be with other frameworks. The first thing we did was isolate the controller from it's service with a mock, but that's not the end of it, since we have to handle the promise it returns by calling .then() on it. So there are a few things to make sure you're doing here:

  1. Have your mock service return a resolved Angular promise by using $q.when('returned data here').
  2. Use $timeout.flush() to force unresolved promises to resolve.

View the complete example on Plunker

Checkout the Gist as well

This is just a start

This blog entry really only covers the basics of testing controllers, there are a great many unique situations that can come up while you're unit testing.

Things to consider:  If it's hard to test, maybe it needs refactored? Anything that's hard to test probably has issues with interdependence or functions that try to do too much in one go and a refactor should be considered.


  1. This is a very clear, up to date tutorial. Thank you.

  2. Illustrative naming and well commented. Up to date. Great tutorial!

  3. Thanks for the post, very well concise and well written

  4. this is brilliant. thanks so much, dude!

  5. Nicest and clearest explanation I've seen so far.

  6. Very nice walk-through for beginners like me. I have one question though - how can I test private functions that are part of controllers and are used in its methods? For example, I want to test the validate function directly ?:

    app.controller('MainCtrl', function($scope, someService) {
    //..... some stuff

    //set up a $watch.
    $scope.$watch('bar', function (v){
    $scope.baz = v + 'baz';

    //make a call to an injected service.
    $scope.test2 = function (){
    //an async call returning a promise that
    //inevitably returns a value to a property.
    $scope.fizz = someService.someAsyncCall($scope.foo);

    function validate() {
    //some logic to test

    1. In that case, you'll want to make validate a member variable of the controller, by declaring it like: this.validate = function (){ /*...*/ };

      After you do that, in the tests, you can call it right off of the ctrl variable:
      it('should validate', function () {

    2. the primary point being, you can't test private functions/methods directly, no matter what language you're developing in... because they're "private" and inaccessible.

  7. I like how you avoid having to deal with the promise by having the controller stick it right into a scope variable and let the view deal with it. Are you able to stick to this pattern all the time or do you ever have to deal with then()s inside the controller? For example error handling. I've been creating mocks that return promises but it makes for a lot of test code and a lot of people don't grock the rootScope.$apply() floating in the middle of the test since it doesn't seem as descriptive as the httpBackend.flush().

    Thanks for the post!

  8. You can do things this way right up to the point that the controller needs to filter or manipulate that returned value in some way. Then you're going to have to call .then() yourself and stick the value in a $scope property. So, for example, if you needed to have some functionality in your controller to aggregate values in a dynamic way, you'd want to be able to test that. It just becomes too much of a pain to deal with if you only have a promise in your scope property.

  9. Simple and great way of explaining Benjamin. This is the best article I've seen so far. Thanks!

  10. Very good, this makes sense and is helping a lot, thanks!

  11. Nice! This blog give me an idea on how to test controller. : )

  12. This tutorial give me a direction. Thank you very much for your work!

  13. Great tutorial for getting me setup with writing my first unit test with Jasmine and Angular. Thanks!

  14. I think your code is not valid anymore. as of angular > 1.2 assigning the promise of you someService.someAsyncCall directly to a $scope value (and eventually resolving to the value in the view) does not work anymore: https://github.com/angular/angular.js/commit/5dc35b527b3c99f6544b8cb52e93c6510d3ac577

    Likewise mocking services to not return promises but raw data is no viable solution anymore? Am i wrong on this?

    1. No, you're right. I haven't come back to blog entry since 1.2 was released. I'll be sure to update it.

  15. Very Good
    But if u post some more post on unit testing with controller,private method etc.
    Do some post on beforeeach and aftereach with example.
    It s more helpfull to developer community.
    At the end,Excellent Work

  16. This message is related to the previous comment.This is the unit test is implement to test this scenario.

    Unit test used to test controller

    "use strict";

    describe("Controller : Login controller", function()
    var $scope, ctrl, $location, $timeout, WebServiceMock;

    beforeEach(module('Apps', function($provide)
    WebServiceMock = jasmine.createSpyObj("WebService", ["loginRequest"]);
    WebServiceMock.loginRequest.andReturn({ UserID:'1', SessionId:'1118430', Status:'LOGIN_SUCCESS', EMail:'v@v.com', FirstName:'Viranga'});
    $provide.value("WebServiceMock", WebServiceMock)


    beforeEach(inject(function($rootScope, $controller, $q, _$timeout_, _$location_, _WebServiceMock_)
    $scope = $rootScope.$new();
    $timeout = _$timeout_;
    $location = _$location_;

    ctrl = $controller('loginCtrl',{
    $scope: $scope,
    $location: $location,
    WebService: _WebServiceMock_,
    $timeout: $timeout


    it('should call loginRequest WebServiceMock', function ()
    $scope.username = 'v@v.com';
    $scope.password = 'viranga123';

    var result =$scope.login();

    //Validate recieved data and Check redirection
    spyOn($scope, '$on').andCallThrough();
    expect(result.Status).toEqual("LOGIN_SUCCESS");//Error here

    Could you please suggest what changes should be made in order to implement this test correctly ?

    1. I'm happy to help, but It's probably best to post all of that on a site like StackOverflow, then just post the link. You'll get help from me, plus a million others.

  17. Sorry for the message clutter.I managed to complete this test by testing the Broadcast event.and i have posted the updated to StackOverflow - http://goo.gl/KB2ga6

  18. Thanks for the post!

    Just keep in mind that andReturn is and.returnValue in Jasmine 2.0.

  19. I think that 'when' function next to someAsyncCall is not covered.

  20. Thanks Ben.

    Here is what I think.

    1. I don't think you need the _$timeout_ to be injected, because in your controller does not use it. Although, the dependency "someService" use it, be it will be mocked later, so it will not use it either. Instead of using "$timeout.flush()", "$scope.$apply();" should be used. In your case it works, because $timeout.flush just happens to do the same dirty checking.

    2. You don't should not create new dependency "someService" like

    someServiceMock = jasmine.createSpyObj('someService', ['someAsyncCall']);

    instead you should let injector inject "someService", and modify some of its methods, like below

    inject(function($rootScope, $controller, $q, someService) {
    spyOn(someService, 'someAsyncCall').and.returnValue($q.when('weee'));

    3. You don't need to inject all dependencies required by MainCtrl, you should comment out the
    the "someService" dependencies. You just partially inject some dependencies, and let angular inject other missing other dependencies like the following.

    ctrl = $controller('MainCtrl', {
    $scope: $scope//,
    //someService: someServiceMock

    1. Thanks for the feedback.

      This article is actually about 2 years old now. Things have changed a bit. At the time of this blog entry, $timeout.flush() was the recommended method for resolving promises when using angular-mocks.

  21. Hi Fred,
    I followed your suggestion and did a:
    var mockedUsers;
    beforeEach(inject(function($q, UserService) {
    mockedUsers = [ { firstname: 'Stephane', lastname: 'Eybert', email: 'mittiprovence@yahoo.se', workPhone: '0667859807' } ];
    spyOn(UserService, 'all').and.returnValue($q.when(mockedUsers));
    it('should return the users', function (){
    But it gives me:
    TypeError: 'undefined' is not an object (evaluating 'spyOn(UserService, 'all').and.returnValue')
    Any clue ?


This form allows some basic HTML. It will only create links if you wrap the URL in an anchor tag (Sorry, it's the Blogger default)