kucukkanat
2/6/2014 - 3:35 PM

Angular - Basics of Unit Testing a Controller

Angular - Basics of Unit Testing a Controller

describe('Testing a controller', function() {
  var $scope, ctrl, $timeout;
  
  /* declare our mocks out here
   * so we can use them through the scope 
   * of this describe block.
   */
  var someServiceMock;
  
 
  // This function will be called before every "it" block.
  // This should be used to "reset" state for your tests.
  beforeEach(function (){
    // Create a "spy object" for our someService.
    // This will isolate the controller we're testing from
    // any other code.
    // we'll set up the returns for this later 
    someServiceMock = jasmine.createSpyObj('someService', ['someAsyncCall']);
    
    // load the module you're testing.
    module('myApp');
    
    // INJECT! This part is critical
    // $rootScope - injected to create a new $scope instance.
    // $controller - injected to create an instance of our controller.
    // $q - injected so we can create promises for our mocks.
    // _$timeout_ - injected to we can flush unresolved promises.
    inject(function($rootScope, $controller, $q, _$timeout_) {
      // create a scope object for us to use.
      $scope = $rootScope.$new();
  
      // set up the returns for our someServiceMock
      // $q.when('weee') creates a resolved promise to "weee".
      // this is important since our service is async and returns
      // a promise.
      someServiceMock.someAsyncCall.andReturn($q.when('weee'));
      
      // assign $timeout to a scoped variable so we can use 
      // $timeout.flush() later. Notice the _underscore_ trick
      // so we can keep our names clean in the tests.
      $timeout = _$timeout_;
      
      // now run that scope through the controller function,
      // injecting any services or other injectables we need.
      // **NOTE**: this is the only time the controller function
      // will be run, so anything that occurs inside of that
      // will already be done before the first spec.
      ctrl = $controller('MainCtrl', {
        $scope: $scope,
        someService: someServiceMock
      });
    });
  });


  /* Test 1: The simplest of the simple.
   * here we're going to test that some things were 
   * populated when the controller function whas evaluated. */
  it('should start with foo and bar populated', function() {
    
    //just assert. $scope was set up in beforeEach() (above)
    expect($scope.foo).toEqual('foo');
    expect($scope.bar).toEqual('bar');
  });
  
  
  /* Test 2: Still simple.
   * Now let's test a simple function call. */
  it('should add !!! to foo when test1() is called', function (){
    //set up.
    $scope.foo = 'x';
    
    //make the call.
    $scope.test1();
    
    //assert
    expect($scope.foo).toEqual('x!!!');
  });
  
  
  /* Test 3: Testing a $watch()
   * The important thing here is to call $apply() 
   * and THEN test the value it's supposed to update. */
  it('should update baz when bar is changed', function (){
    //change bar
    $scope.bar = 'test';
    
    //$apply the change to trigger the $watch.
    $scope.$apply();
    
    //assert
    expect($scope.baz).toEqual('testbaz');
  });
  
  
  /* Test 4: Testing an asynchronous service call.
     Since we've mocked the service to return a promise
     (just like the original service did), we need to do a little
     trick with $timeout.flush() here to resolve our promise so the
     `then()` clause in our controller function fires. 
     
     This will test to see if the `then()` from the promise is wired up
     properly. */
  it('should update fizz asynchronously when test2() is called', function (){
    // just make the call
    $scope.test2();
    
    // asser that it called the service method.
    expect(someServiceMock.someAsyncCall).toHaveBeenCalled();  
    
    // call $timeout.flush() to flush the unresolved dependency from our
    // someServiceMock.
    $timeout.flush();
    
    // assert that it set $scope.fizz
    expect($scope.fizz).toEqual('weee');    
  });
});
<!DOCTYPE html>
<html>

<head>
  <!-- jasmine -->
  <script src="//cdnjs.cloudflare.com/ajax/libs/jasmine/1.3.1/jasmine.js"></script>
  <!-- jasmine's html reporting code and css -->
  <script src="//cdnjs.cloudflare.com/ajax/libs/jasmine/1.3.1/jasmine-html.js"></script>
  <link href="//cdnjs.cloudflare.com/ajax/libs/jasmine/1.3.1/jasmine.css" rel="stylesheet" />
  <!-- angular itself -->
  <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.11/angular.js"></script>
  <!-- angular's testing helpers -->
  <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.11/angular-mocks.js"></script>
  <!-- your angular app code -->
  <script src="app.js"></script>
  <!-- your Jasmine specs (tests) -->
  <script src="specs.js"></script>
</head>
<body>
  <!-- bootstrap jasmine! -->
  <script>
  var jasmineEnv = jasmine.getEnv();
  
  // Tell it to add an Html Reporter
  // this will add detailed HTML-formatted results
  // for each spec ran.
  jasmineEnv.addReporter(new jasmine.HtmlReporter());
  
  // Execute the tests!
  jasmineEnv.execute();
  </script>
</body>
</html>
var app = angular.module('myApp', []);

/* Set up a simple controller with a few 
 * examples of common actions a controller function
 * might set up on a $scope. */
app.controller('MainCtrl', function($scope, someService) {
  
  //set some properties
  $scope.foo = 'foo';
  $scope.bar = 'bar';
  
  
  //add a simple function.
  $scope.test1 = function (){
    $scope.foo = $scope.foo + '!!!';
  };
  
  //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.
    someService.someAsyncCall($scope.foo)
      .then(function(data) {
        $scope.fizz = data;
      });
  };
});


/* Simple service example. 
 * This is a service created just to use as an example of
 * some simple service that is making some asynchronous call.
 * A real-life example of something like this would be a 
 * service that is making $http or $resource calls, perhaps. */
app.factory('someService', function ($timeout, $q){
  return {
    
    // simple method to do something asynchronously.
    someAsyncCall: function (x){
      var deferred = $q.defer();
      $timeout(function (){
        deferred.resolve(x + '_async');
      }, 100);
      return deferred.promise;
    }
  };
});