Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AngularJS error: Unexpected request (No more requests expected)

In my AngularJS application, I'm having trouble figuring out how to unit test that the execution of a then promise changes a location.url. I have a function, login, that calls a service, AuthenticationService. It returns the promise, and the execution of the then promise changes the location.url.

This is the controller 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');
      });
  };
 }
]);

This is my attempt at the unit test:

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

When I run this test, I get this error. I get the same error if I use rootScope.$apply() instead:

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

When I take out scope.$apply(), I get this error:

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

Any ideas on how to fix this? This is the first time that I'm unit testing a promise, and I'm probably conceptually misunderstanding how to do this.

Thank you in advance for your help.

like image 733
cvk10 Avatar asked Dec 14 '14 15:12

cvk10


2 Answers

When you're unit-testing with AngularJS, you're actually using mock services from ngMock module instead of real services. In this case, your error probably comes form $httpBackend mock, which has to be "primed" first, if you're expecting the tested units to use $http to send real requests. (I don't see your code using $http anywhere, so I'm guessing there's a part you're not showing here.)

Basically, you need to tell the $httpBackend mock that you're expecting a particular request using $httpBackend.when (or you can use $httpBackend.expect, if you want the mock to check that the request was really called) and what response should be returned.

So, your error has nothing to do with promises and everything to do with testing units that connect to server.

like image 136
hon2a Avatar answered Nov 08 '22 20:11

hon2a


Maybe a moot point to be answering the question nearly two years after it has been asked, but the accepted answer is not quite right. The problem is that you are not properly mocking out AuthenticationService. You should completely mock out the functionality of the service, testing only that the service has been called from the controller, and save the $httpBackend stuff for testing the service itself. What you want to do is inject a fake AuthenticationService (as well as mock instantiations of Angular services such as $q, $location, and $route, if we're following best practices) into the beforeEach, and then declare it in the $controller definition. You can also include your spies in the beforeEach block.

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

Note that it's important to make your controller definition the last item in the beforeEach block if you are defining your variables this way, or else the mocks won't be properly instantiated and the service methods will actually be called, regardless of spies.

like image 1
Alex Erickson Avatar answered Nov 08 '22 22:11

Alex Erickson