Search code examples
angularjskarma-runnerhttpbackend

Karma: trouble modularizing testing of requests


Here is the controller I'm testing:

angular
  .module('mean-starter')
  .controller('AdminController', AdminController);

function AdminController(User, Auth, $state) {
  var vm = this;
  User
    .list()
    .success(function(data) {
      vm.users = data;
    })
    .error(function() {
      console.log('Problem getting users.');
    });

  vm.delete = function(id) {
    User
      .delete(id)
      .success(function(data) {
        if (Auth.getCurrentUser()._id === id) Auth.logout(); // deleting yourself
        else $state.reload();
      })
      .error(function() {
        console.log('Problem deleting user.');
      });
  };
}

And my tests:

describe('AdminController', function() {
  var createController, scope, $httpBackend;

  beforeEach(module('mean-starter'));
  beforeEach(module('templates'));
  beforeEach(inject(function($controller, $rootScope, _$httpBackend_) {
    $httpBackend = _$httpBackend_;
    scope = $rootScope.$new();
    createController = function() {
      return $controller('AdminController');
    }
  }));

  afterEach(function() {
    $httpBackend.verifyNoOutstandingExpectation();
    $httpBackend.verifyNoOutstandingRequest();
  });

  it('gets users', function() {
    var users = [ 'user1', 'user2', 'user3' ];
    $httpBackend
      .expectGET('/users')
      .respond(users);
    var adminController = createController();
    $httpBackend.flush();
    expect(adminController.users).toEqual(users);
  });

  it('has a method that deletes users', function() {
    $httpBackend.expectDELETE('/users/1').respond();
    $httpBackend.expectGET('/current-user').respond();
    $httpBackend.expectGET('/logout').respond();
    var adminController = createController();
    adminController.delete(1);
    $httpBackend.flush();
  });
});

It's currently failing:

enter image description here

It wants me to have an $httpBackend.expectGET('/users') in my second it block. I don't want to do that. To me, it seems more modular and organized if each it block tests a separate thing.


1) What should I do?

2) Why am I getting the $digest already in progress error?


Solution

    1. You're right in that each it should test a separate thing. The it blocks are separate tests, they don't (shouldn't) depend on each other. The second test creates a new controller instance, which in turn tries to load users immediately. Even if you're not interested in this request here, you still need to set up the mock that handles it. You can also use the .when(...).respond(...) form instead of expect.

    EDIT: One way to make this nicer would be to move the mocking of GET into a beforeEach as $httpBackend.expectGET('/users').respond(users). This way, the mock for the first GET would work for all it tests, so when testing something else, you wouldn't need to worry about it. As for the first it, maybe it would be enough to only do expect(adminController.users).toEqual(users). (There would be no explicit expectation about how the controller gets the users, only about the value -- but that obviously comes from the GET, so the coverage would be essentially the same.)

    1. Could it be something like this: https://github.com/angular/angular.js/pull/6522 ?