简体   繁体   English

如何使用ui-router测试依赖于服务的AngularJS控制器?

[英]How can I test an AngularJS controller that relies on a service, using ui-router?

My test is: 我的测试是:

(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');
    });
  });
})();

Simple enough, I suppose? 我想,这很简单吗? My controller is: 我的控制器是:

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();
  }
]);

What I get in my test is: 我在测试中得到的是:

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

The reason this happens is because the state transitioned to dashboard , which then tried to load the dashboard.html template. 发生这种情况的原因是状态转换为dashboard ,然后dashboard尝试加载dashboard.html模板。 I don't necessarily want to / need to test that. 我不一定要/需要测试。 I just need to test that it got to the right state. 我只需要测试它是否达到了正确的状态。 How can I set this up properly? 如何正确设置?

I'm using the Angular UI Router instead of the built in $routeProvider 我正在使用Angular UI路由器而不是内置的$routeProvider

The short answer is that you need to tell $httpBackend to expect the request. 简短的回答是你需要告诉$ httpBackend来期待这个请求。 Karma will throw an error if any request is sent and not caught by $httpBackend, as they should all be mocked in unit tests. 如果任何请求被发送并且没有被$ httpBackend捕获,Karma将抛出错误,因为它们都应该在单元测试中被模拟。 It might look something like... 它可能看起来像......

$httpBackend.expectGET('/templates/dashboard.html');
$scope.login();
$httpBackend.flush();

I say this is only the short answer because there are a few things going on here and it would be a bit of a disservice not to mention them. 我说这只是简短的答案,因为这里有一些事情,如果不提及它们会有点不利。 First, you are using $httpBackend to catch the authentication POST request, however you aren't actually sending the request, because you are mocking the userService. 首先,您使用$ httpBackend来捕获身份验证POST请求,但是您实际上并没有发送请求,因为您正在模拟userService。 You don't need to do both. 你不需要两者都做。 You can remove this code... 你可以删除这段代码......

return $httpBackend.whenPOST('http://localhost:9001/api/v1/session/check').respond({
    authenticated: true
});

since that request won't actually be sent by your mock user service. 因为您的模拟用户服务实际上不会发送该请求。 That code might be useful in your userService's unit tests though. 但是,该代码可能在您的userService的单元测试中很有用。

I would also make use of the beforeEach/afterEach hooks. 我还会使用beforeEach / afterEach挂钩。 There is no need to call self.createController in all of your tests when you can call it once in the beforeEach function and it will run before each test. 当你可以在beforeEach函数中调用一次并且它将在每次测试之前运行时,不需要在所有测试中调用self.createController。 This could get messy if you aren't careful. 如果你不小心,这可能会变得混乱。

Finally, testing that a view changes and a new page title is set is pushing the limits of what unit tests are for. 最后,测试视图更改并设置新的页面标题正在推动单元测试的极限。 Ideally, you would test routing and page changes in integration tests, if at all. 理想情况下,您可以在集成测试中测试路由和页面更改(如果有的话)。 The only thing you are really testing in that second test is that... 你在第二次测试中唯一真正测试的是......

$state.transitionTo('dashboard')

does what its supposed to (updates the current $state to 'dashboard'). 做它应该做的(将当前的$状态更新为'dashboard')。 You should trust this works. 你应该相信这是有效的。 UI-router has its own tests that prove this works. UI路由器有自己的测试证明这是有效的。 I would strive to make your unit tests as small as possible and only test the specific behavior of the individual components of your application in isolation. 我会努力使您的单元测试尽可能小,并且仅隔离测试应用程序各个组件的特定行为。 So instead of asking.. 所以不要问......

"When I call $scope.login(), does the userService's login function get called and send a POST request and change the state?" “当我调用$ scope.login()时,是否会调用userService的登录函数并发送POST请求并更改状态?”

I would simply ask... 我只想问......

"When I call $scope.login(), does the userService's login function get called?" “当我调用$ scope.login()时,是否会调用userService的登录函数?”

The rest is not the responsibility of your controller. 其余部分不是您的控制器的责任。 Whether the request is sent is the responsibility of the userService (and should be tested there) and whether the state changes when you call $state.transitionTo is the scope of ui-router and is already tested there. 是否发送请求是userService的责任(并且应该在那里进行测试)以及当你调用$ state.transitionTo时状态是否发生变化是ui-router的范围并且已经在那里进行了测试。 Leave the high level tests that span multiple components for integration tests. 保留跨越多个组件的高级测试以进行集成测试。

You can mock out your template file and thus avoid the HTTP error with $templateCache : 您可以模拟您的模板文件,从而避免使用$templateCache的HTTP错误:

$templateCache = $injector.get('$templateCache');
$templateCache.put('templates/dashboard.html','<div>blank or whatever</div>');

... another way to do it is with $httpBackend ... ...另一种方法是使用$httpBackend ......


BTW, instead of using $injector all the time you can just add the injectables to your inject function... 顺便说一下,你可以直接将注射剂添加到inject功能中,而不是一直使用$injector ......

beforeEach(inject(function($rootScope, $state, $httpBackend, $templateCache) {

I often use the underscore syntax for additional flexibility: 我经常使用下划线语法来获得额外的灵活性:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM