Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I spy on a controller method using Jasmine?

I'm using the "controller as" syntax to create my controller. I have a private initialization function that calls a function to load the default data.

var app = angular.module('plunker', []);

app.controller('MainCtrl', function($scope) {
  var mc = this;
  mc.dataLoaded = false;

  function init() {
    mc.loadData();
  }

  mc.loadData = function(){
    mc.dataLoaded = true;
  }

  init();
});

In my test I'm creating a spy to check whether the loadData function has been called. Although I can verify that the function has been called by testing for the mc.dataLoaded flag, my spy doesn't seem to record the function being called. How can I get the spy to correctly record the function call?

describe('Testing a Hello World controller', function() {
  var $scope = null;
  var ctrl = null;

  //you need to indicate your module in a test
  beforeEach(module('plunker'));

  beforeEach(inject(function($rootScope, $controller) {
    $scope = $rootScope.$new();

    ctrl = $controller('MainCtrl as mc', {
      $scope: $scope
    });

    spyOn($scope.mc, 'loadData').and.callThrough();
  }));

  it('should call load data', function() {
    expect($scope.mc.loadData).toHaveBeenCalled();
  //expect($scope.mc.dataLoaded).toEqual(false);
  });
});

Plunker link

like image 276
Kmart2k1 Avatar asked Feb 25 '15 22:02

Kmart2k1


2 Answers

This sequence of lines:

ctrl = $controller('MainCtrl as mc', {
  $scope: $scope
});

spyOn($scope.mc, 'loadData').and.callThrough();

Means that the Jasmine spy is created after the controller has already been instantiated by $controller. Before the spy is created, the init function has already executed.

You can't switch the lines around either, because MainCtrl needs to exist before you can spy on a method on it.

If the init function calls another service, then spy on that service's method and assert that the service is called correctly. If MainCtrl is just doing something internally, then test the result of that, for example, by asserting that controller's data/properties are updated. It may not even be worth testing if it's trivial enough.

Also, since you're using the controller as syntax, you can reference the controller through the return value of calling $controller, rather than accessing the scope directly:

ctrl = $controller('MainCtrl as mc', {
  $scope: $scope
});

ctrl.loadData === $scope.mc.loadData; // true
like image 137
user2943490 Avatar answered Oct 03 '22 19:10

user2943490


I found a solution that allowed me to avoid changing my controller. I included a $state mock service in the test suite's beforeEach method, and gave it a reload mock method:

beforeEach(inject(function ($controller, $rootScope) {
    stateMock = {
        reload: function() {
            myCtrl = $controller('MyCtrl');
        }
    };
    ...

Then within the jasmine tests, I can simply call stateMock.reload() to re-initialize my controller while preserving my spies I declared in another beforeEach block.

like image 39
dpfrakes Avatar answered Oct 03 '22 21:10

dpfrakes