AngularJS错误:意外请求(不再需要请求)

时间:2014-12-14 15:53:11

标签: angularjs unit-testing jasmine

在我的AngularJS应用程序中,我无法弄清楚如何进行单元测试,然后承诺的执行会改变location.url。我有一个功能登录,它可以调用服务 AuthenticationService 。它返回promise,而then promise的执行会改变location.url。

这是控制器 AuthCtrl

angular
  .module('bloc1App')
  .controller('AuthCtrl', ['$scope', '$http','$location', 'AuthenticationService',
  function($scope, $http, $location, AuthenticationService) { 

   this.AuthenticationService = AuthenticationService;
   var self = this;

  $scope.login = function(username, password){
    self.AuthenticationService
      .login(username, password)
      .then(function(){
        $location.url('/tasks');
      });
  };
 }
]);

这是我在单元测试中的尝试:

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

      var scope,
          location,
          route,
          q,
          rootScope,
          AuthCtrl;

      beforeEach(module('bloc1App'));

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

      AuthCtrl = $controller('AuthCtrl', {
         $scope: scope,
         $route: route,
         $location: location
      });

    }));

    it('login should redirect user to tasks view', function(){
      var deferred = q.defer();
      spyOn(AuthCtrl.AuthenticationService, 'login').andReturn(deferred.promise);
      spyOn(location, 'url');
      scope.login('foo', 'foo');
      deferred.resolve();
      scope.$apply();
      expect(location.url).toHaveBeenCalledWith('/tasks');
    });
   });

当我运行此测试时,我收到此错误。如果我使用rootScope,我会得到同样的错误。$ apply()代替:

Error: Unexpected request: GET views/AuthView.html
No more request expected

当我拿出范围。$ apply()时,我收到此错误:

Expected spy url to have been called with [ '/tasks' ] but it was never called.

有关如何解决此问题的任何想法?这是我第一次单独测试一个承诺,我可能在概念上误解了如何做到这一点。

提前感谢您的帮助。

2 个答案:

答案 0 :(得分:4)

当你使用AngularJS进行单元测试时,你实际上是使用来自ngMock模块的模拟服务而不是真实服务。在这种情况下,您的错误可能来自$httpBackend mock,如果您希望测试单位使用$http发送实际请求,则必须首先“准备”。 (我没有在任何地方看到你的代码使用$http,所以我猜你有一部分你没有在这里展示。)

基本上,您需要告诉$httpBackend模拟您使用$httpBackend.when期望特定请求(或者您可以使用$httpBackend.expect,如果您希望模拟检查请求真的被调用了)应该返回什么样的响应。

因此,您的错误与承诺无关,也与连接到服务器的测试单元有关。

答案 1 :(得分:1)

在问到这个问题差不多两年之后,也许是一个有争议的问题,但是接受的答案并不完全正确。问题是你没有正确地嘲笑AuthenticationService。您应该完全模拟服务的功能,仅测试 从控制器调用服务,并保存$httpBackend内容以测试服务本身。你要做的是注入假AuthenticationService(以及$q$location$route等Angular服务的模拟实例,如果我们追随最好的实践)到beforeEach,然后在$controller定义中声明它。您还可以在beforeEach区块中包含您的间谍。

describe('AuthCtrl', function(){

  var $q;
  var $location;
  var $controller;
  var AuthenticationService;
  var $route;
  var deferred;

  beforeEach(inject(function( _$controller_, $rootScope, _AuthenticationService_, _$q_, _$location_, _$route_){

    scope = $rootScope.$new();
    AuthenticationService = _AuthenticationService_;
    $location = _$location_;
    $route = _$route_;
    $controller = _$controller_;
    $q = _$q_;
    deferred = $q.defer();

    spyOn(AuthenticationService, 'login').andReturnValue(deferred.promise);
    spyOn($location, 'url');

    AuthCtrl = $controller('AuthCtrl', {
      scope: scope,
      AuthenticationService: AuthenticationService,
      $route: $route,
      $location: $location
    };

  }));

  it('should redirect the user on login', function(){
      deferred.resolve();
      scope.$apply();
      expect($location.url).toHaveBeenCalledWith('/tasks');
  }); 
});

请注意,如果您以这种方式定义变量,那么将控制器定义作为beforeEach块中的最后一项非常重要,否则将无法正确实例化模拟并实际调用服务方法,不管是间谍。