admin管理员组

文章数量:1352849

Similar question to: Jasmine angularjs - spying on a method that is called when controller is initialized

In my controller I use the angular-local-storage package to provide a localStorageService through injection.

in my unit tests I want to make sure the data is retrieved from the service, so I spy on the "get" and "add" methods and mock them (.andCallFake).

this works fine with all the methods that are called through $scope.$watch - as long as I force a $digest. but for the method that is called on the controller initialization, it does not seem to work. Can anyone advise why this doesn't work?

app>Main.js

angular.module('angularTodoApp')
  .controller('MainCtrl',['$scope','localStorageService',function ($scope, localStorageService) {

    var todosInStore =  localStorageService.get('todos');
    $scope.todos = todosInStore && todosInStore.split('\n') || [];
    //$scope.todos = ['Item 1', 'Item 2', 'Item 3'];

    $scope.$watch('todos', function(){
      localStorageService.add('todos', $scope.todos.join('\n'));
    },true);

    $scope.addTodo  = function() {
      $scope.todos.push($scope.todo);
      $scope.todo = '';
    };

    $scope.removeTodo = function(index) {
      $scope.todos.splice(index,1);
    };
  }]);

test>Main.js

describe('Controller: MainCtrl', function () {

  // load the controller's module
  beforeEach(module('angularTodoApp'));

  var MainCtrl,
    scope,
    localStorageService,
    store = [];

  // Initialize the controller and a mock scope
  beforeEach(inject(function ($controller, $rootScope, _localStorageService_) {
    scope = $rootScope.$new();
    localStorageService = _localStorageService_;
    MainCtrl = $controller('MainCtrl', {
      $scope: scope,
      localStorageService: _localStorageService_
    });

    //mock localStorageService get/add
    spyOn(localStorageService,'get').andCallFake(function(key){
      return store[key];
    });
    spyOn(localStorageService,'add').andCallFake(function(key, val){
      store[key] = val;
    });
  }));

  it('should retrieve "todos" from the store and assign to scope', function () {
    expect(localStorageService.get).toHaveBeenCalledWith('todos');
    expect(scope.todos.length).toBe(0);
  });

  it('should add items to the list and update the store for key = "todos"', function () {
    scope.todo = 'Test 1';
    scope.addTodo();
    scope.$digest();
    expect(localStorageService.add).toHaveBeenCalledWith('todos', jasmine.any(String));
    expect(scope.todos.length).toBe(1);
  });

all tests pass except the one in the constructor:

expect(localStorageService.get).toHaveBeenCalledWith('todos');

Similar question to: Jasmine angularjs - spying on a method that is called when controller is initialized

In my controller I use the angular-local-storage package to provide a localStorageService through injection.

in my unit tests I want to make sure the data is retrieved from the service, so I spy on the "get" and "add" methods and mock them (.andCallFake).

this works fine with all the methods that are called through $scope.$watch - as long as I force a $digest. but for the method that is called on the controller initialization, it does not seem to work. Can anyone advise why this doesn't work?

app>Main.js

angular.module('angularTodoApp')
  .controller('MainCtrl',['$scope','localStorageService',function ($scope, localStorageService) {

    var todosInStore =  localStorageService.get('todos');
    $scope.todos = todosInStore && todosInStore.split('\n') || [];
    //$scope.todos = ['Item 1', 'Item 2', 'Item 3'];

    $scope.$watch('todos', function(){
      localStorageService.add('todos', $scope.todos.join('\n'));
    },true);

    $scope.addTodo  = function() {
      $scope.todos.push($scope.todo);
      $scope.todo = '';
    };

    $scope.removeTodo = function(index) {
      $scope.todos.splice(index,1);
    };
  }]);

test>Main.js

describe('Controller: MainCtrl', function () {

  // load the controller's module
  beforeEach(module('angularTodoApp'));

  var MainCtrl,
    scope,
    localStorageService,
    store = [];

  // Initialize the controller and a mock scope
  beforeEach(inject(function ($controller, $rootScope, _localStorageService_) {
    scope = $rootScope.$new();
    localStorageService = _localStorageService_;
    MainCtrl = $controller('MainCtrl', {
      $scope: scope,
      localStorageService: _localStorageService_
    });

    //mock localStorageService get/add
    spyOn(localStorageService,'get').andCallFake(function(key){
      return store[key];
    });
    spyOn(localStorageService,'add').andCallFake(function(key, val){
      store[key] = val;
    });
  }));

  it('should retrieve "todos" from the store and assign to scope', function () {
    expect(localStorageService.get).toHaveBeenCalledWith('todos');
    expect(scope.todos.length).toBe(0);
  });

  it('should add items to the list and update the store for key = "todos"', function () {
    scope.todo = 'Test 1';
    scope.addTodo();
    scope.$digest();
    expect(localStorageService.add).toHaveBeenCalledWith('todos', jasmine.any(String));
    expect(scope.todos.length).toBe(1);
  });

all tests pass except the one in the constructor:

expect(localStorageService.get).toHaveBeenCalledWith('todos');
Share Improve this question edited May 23, 2017 at 11:59 CommunityBot 11 silver badge asked Apr 13, 2014 at 22:10 Vincent De SmetVincent De Smet 4,9792 gold badges36 silver badges41 bronze badges
Add a ment  | 

3 Answers 3

Reset to default 5

The reason is that the controller is initialised at the point you call $controller, which in your example is before you're created the spies via spyOn. The solution for your example is to move the call to $controller to after the calls to spyOn.

For longer test suites, to keep things DRY, you might have to put the call to $controller in a separate function, that you can then call after you have mocked up any required services.

Updated test suite, make sure mocks are in place before calling controller constructor:

describe('Controller: MainCtrl', function () {

  // load the controller's module
  beforeEach(module('angularTodoApp'));

  var MainCtrl,
    scope,
    localStorageService,
    store;

  // Initialize the controller and mocks
  beforeEach(inject(function ($controller, $rootScope, _localStorageService_) {
    store = []; //clear the store before each test
    scope = $rootScope.$new();
    localStorageService = _localStorageService_;

    //mock localStorageService get/add
    spyOn(localStorageService,'get').andCallFake(function(key){
      return store[key];
    });
    spyOn(localStorageService,'add').andCallFake(function(key, val){
      store[key] = val;
    });

    //Instantiate controller to test
    MainCtrl = $controller('MainCtrl', {
      $scope: scope,
      localStorageService: localStorageService
    });
  }));

  it('should retrieve "todos" from the store and assign to scope', function () {
    expect(localStorageService.get).toHaveBeenCalledWith('todos');
    expect(scope.todos.length).toBe(0);
  });

  it('should add items to the list and update the store for key = "todos"', function () {
    scope.todo = 'Test 1';
    scope.addTodo();
    scope.$digest();
    expect(localStorageService.add).toHaveBeenCalledWith('todos', jasmine.any(String));
    expect(scope.todos.length).toBe(1);
  });

  it('should remove items to the list and update the store', function() {
    scope.todo = 'Test 1';
    scope.addTodo();
    scope.$digest();
    //reset call count
    localStorageService.add.reset();

    scope.removeTodo(0);
    scope.$digest();
    expect(localStorageService.add).toHaveBeenCalledWith('todos', jasmine.any(String));
    expect(scope.todos.length).toBe(0);
  });
});

You also need to include the LocalStorageModule in your beforeEach() call for loading the module in test.js file.

beforeEach(module('angularTodoApp', 'LocalStorageModule'));

Also, don't forget to update your karma.conf.js file

module.exports = function(config) {
    // ...
    files: [
        '/path/to/angular.min.js',
        '/path/to/angular-mocks.js',
        '/path/to/angular-local-storage.js',
        'Main/test.js',
        'Main/app.js'
    ],
    //...

I just stumbled on the same problem, this did the trick for me.

Finally, you also need to modify the spyOn(...).andCallFake(...) call if you're using Jasmine 2.4 with spyOn(...).and.callFake(...)

The method given in the previous answers is not working in Jasmine 2.4 (not tested in previous versions though).

本文标签: