Javascript 如何使用ui路由器测试依赖于服务的AngularJS控制器?

Javascript 如何使用ui路由器测试依赖于服务的AngularJS控制器?,javascript,angularjs,unit-testing,karma-runner,Javascript,Angularjs,Unit Testing,Karma Runner,我的测试是: (function() { describe('Login Controller', function() { beforeEach(module('myApp')); beforeEach(inject(function($injector) { var $controller, $httpBackend, $q, userServiceMock; self.$rootScope = $injector.get('$rootScope'

我的测试是:

(function() {
  describe('Login Controller', function() {
    beforeEach(module('myApp'));
    beforeEach(inject(function($injector) {
      var $controller, $httpBackend, $q, userServiceMock;
      self.$rootScope = $injector.get('$rootScope');
      self.$scope = $rootScope.$new();
      self.$state = $injector.get('$state');
      $q = $injector.get('$q');
      $httpBackend = $injector.get('$httpBackend');
      $controller = $injector.get('$controller');
      userServiceMock = {
        login: function(auth) {
          self.deferred = $q.defer();
          console.log("HERE!");
          return self.deferred.promise;
        }
      };
      self.createController = function() {
        return $controller('LoginController', {
          '$scope': self.$scope,
          '$rootScope': self.$rootScope,
          '$state': self.$state,
          'userService': userServiceMock
        });
      };
      return $httpBackend.whenPOST('http://localhost:9001/api/v1/session/check').respond({
        authenticated: true
      });
    }));
    it('should set the page title to "Login"', function() {
      self.createController();
      $scope.init();
      expect($rootScope.pageTitle).toBe('Login');
      return expect($scope.auth).toEqual({});
    });
    return it('should properly authenticate a user', function() {
      self.createController();
      $scope.init();
      $scope.auth = {
        username: 'test@test.com',
        password: 'mypassword'
      };
      $scope.login();
      self.deferred.resolve({
        authenticated: true
      });
      $scope.$root.$digest();
      console.log($state.current.name);
      expect($state.current.name).toBe('dashboard');
    });
  });
})();
NFO [karma]: Karma v0.12.16 server started at http://localhost:9876/
INFO [launcher]: Starting browser PhantomJS
INFO [PhantomJS 1.9.7 (Mac OS X)]: Connected on socket TRjrAWL8PHdQ-bKqkyzK with id 61166122
LOG: 'HERE!'
PhantomJS 1.9.7 (Mac OS X) - Login Controller:
PhantomJS 1.9.7 (Mac OS X)  should set the page title to "Login" PASSED
PhantomJS 1.9.7 (Mac OS X)  should properly authenticate a user FAILED
PhantomJS 1.9.7 (Mac OS X): Executed 2 of 2 (1 FAILED) (0.02 secs / 0.018 secs)
PhantomJS 1.9.7 (Mac OS X)
PhantomJS 1.9.7 (Mac OS X) Login Controller should properly authenticate a user FAILED
    Error: Unexpected request: GET templates/dashboard.html
    No more request expected
我想很简单吧?我的控制器是:

myApp.controller('LoginController', [
  '$scope', '$rootScope', '$state', 'userService', function($scope, $rootScope, $state, userService) {
    $scope.init = function() {
      $rootScope.pageTitle = 'Login';
      return $scope.auth = {};
    };
    $scope.login = function() {
      return userService.login($scope.auth).then(function(response) {
        if (response.authenticated === true) {
          return $state.transitionTo('dashboard');
        } else {
          return console.log('bad password man');
        }
      });
    };
    return $scope.init();
  }
]);
我在测试中得到的是:

(function() {
  describe('Login Controller', function() {
    beforeEach(module('myApp'));
    beforeEach(inject(function($injector) {
      var $controller, $httpBackend, $q, userServiceMock;
      self.$rootScope = $injector.get('$rootScope');
      self.$scope = $rootScope.$new();
      self.$state = $injector.get('$state');
      $q = $injector.get('$q');
      $httpBackend = $injector.get('$httpBackend');
      $controller = $injector.get('$controller');
      userServiceMock = {
        login: function(auth) {
          self.deferred = $q.defer();
          console.log("HERE!");
          return self.deferred.promise;
        }
      };
      self.createController = function() {
        return $controller('LoginController', {
          '$scope': self.$scope,
          '$rootScope': self.$rootScope,
          '$state': self.$state,
          'userService': userServiceMock
        });
      };
      return $httpBackend.whenPOST('http://localhost:9001/api/v1/session/check').respond({
        authenticated: true
      });
    }));
    it('should set the page title to "Login"', function() {
      self.createController();
      $scope.init();
      expect($rootScope.pageTitle).toBe('Login');
      return expect($scope.auth).toEqual({});
    });
    return it('should properly authenticate a user', function() {
      self.createController();
      $scope.init();
      $scope.auth = {
        username: 'test@test.com',
        password: 'mypassword'
      };
      $scope.login();
      self.deferred.resolve({
        authenticated: true
      });
      $scope.$root.$digest();
      console.log($state.current.name);
      expect($state.current.name).toBe('dashboard');
    });
  });
})();
NFO [karma]: Karma v0.12.16 server started at http://localhost:9876/
INFO [launcher]: Starting browser PhantomJS
INFO [PhantomJS 1.9.7 (Mac OS X)]: Connected on socket TRjrAWL8PHdQ-bKqkyzK with id 61166122
LOG: 'HERE!'
PhantomJS 1.9.7 (Mac OS X) - Login Controller:
PhantomJS 1.9.7 (Mac OS X)  should set the page title to "Login" PASSED
PhantomJS 1.9.7 (Mac OS X)  should properly authenticate a user FAILED
PhantomJS 1.9.7 (Mac OS X): Executed 2 of 2 (1 FAILED) (0.02 secs / 0.018 secs)
PhantomJS 1.9.7 (Mac OS X)
PhantomJS 1.9.7 (Mac OS X) Login Controller should properly authenticate a user FAILED
    Error: Unexpected request: GET templates/dashboard.html
    No more request expected
发生这种情况的原因是,状态转换为
dashboard
,然后尝试加载
dashboard.html
模板。我不一定想/需要测试。我只需要测试它是否达到了正确的状态。我怎样才能正确地设置它


我使用的是而不是内置的
$routeProvider

您可以模拟模板文件,从而避免使用
$templateCache
的HTTP错误:

$templateCache = $injector.get('$templateCache');
$templateCache.put('templates/dashboard.html','<div>blank or whatever</div>');
我经常使用下划线语法来增加灵活性:

beforeEach(inject(function(_$rootScope_, _$state_, _$httpBackend_, _$templateCache_) {

简单的回答是,您需要告诉$httpBackend以预期请求。如果任何请求被发送并且没有被$httpBackend捕获,Karma将抛出一个错误,因为它们都应该在单元测试中被模拟。它可能看起来像

$httpBackend.expectGET('/templates/dashboard.html');
$scope.login();
$httpBackend.flush();
我说这只是一个简短的答案,因为这里发生了一些事情,不提它们会有点伤害。首先,您使用$httpBackend捕获身份验证POST请求,但实际上并没有发送请求,因为您在模拟userService。你不需要两者都做。您可以删除此代码

return $httpBackend.whenPOST('http://localhost:9001/api/v1/session/check').respond({
    authenticated: true
});
因为该请求实际上不会由模拟用户服务发送。不过,该代码在用户服务的单元测试中可能很有用

我也会使用beforeach/afterEach挂钩。如果可以在beforeEach函数中调用self.createController一次,并且它将在每次测试之前运行,则无需在所有测试中调用self.createController。如果你不小心,这可能会变得一团糟

最后,测试一个视图的变化和一个新的页面标题的设置是对单元测试的限制。理想情况下,您可以在集成测试中测试路由和页面更改(如果有的话)。你在第二次测试中真正测试的唯一一件事是

$state.transitionTo('dashboard')
执行它应该执行的操作(将当前$state更新为“dashboard”)。你应该相信这是有效的。UI路由器有自己的测试来证明这一点。我将努力使您的单元测试尽可能小,并且只单独测试应用程序各个组件的特定行为。因此,与其问

“当我调用$scope.login()时,是否调用userService的登录函数并发送POST请求并更改状态?”

我只想问

调用$scope.login()时,是否调用userService的登录函数


其余部分不由您的控制员负责。是否发送请求是userService的责任(应在此处进行测试),以及在调用$state.Transitiono时状态是否更改是ui router的范围,并且已经在此处进行了测试。将跨多个组件的高级测试留给集成测试。

您能详细说明增加的灵活性吗?我的意思是,它使您能够灵活地命名inject块之外声明的变量
$rootScope
$state
,等等。