2

I'm writing tests for my login controller in my angular application, and during the "login fails with invalid credentials" test, the API returns a 401. The problem is the auth interceptor (whose job it is to pick up 401s), intervenes and tries to execute dependencies that haven't been correctly injected.

Here's how the interceptor is injected in my app.js:

$httpProvider.interceptors.push('authInterceptor');

And here's the guts of the interceptor (it's a factory):

return {
    response: function(response){
        return response || $q.when(response);
    },
    responseError: function(rejection) {
        if (rejection.status === 401 && $location.path() !== '/login') {
            // Clear user state.
            delete $rootScope.loggedUser;
            storage.clearAll();
            document.cookie = "my_session=; expires=Thu, 01 Jan 1970 00:00:00 UTC";

            // Redirect.
            $location.path("/login");
        }
        return $q.reject(rejection);
    }
}

And here's my test (running against the login controller, not the above interceptor. Ideally the interceptor should never even execute in this test case):

it('should fail with invalid credentials', inject(function($httpBackend, $location, storage) {
    $httpBackend.expectPOST('/api/login').respond(401);
    scope.loginCredentials = {email : 'email@email.com', password : 'password'};
    scope.login(scope.loginCredentials);
    $httpBackend.flush();
}));

My mock of the storage library is injected like so-

var LoginCtrl,
    mockStorage,
    store,
    scope;

// Initialize the controller and a mock scope
beforeEach(inject(function ($controller, $rootScope, storage, User) {
    scope = $rootScope.$new();
    store = [];

    mockStorage = storage;
    spyOn(mockStorage,'get').andCallFake(function(key){
        return store[key];
    });
    spyOn(mockStorage,'set').andCallFake(function(key, val){
        store[key] = val;
    });

    LoginCtrl = $controller('LoginCtrl', {
        $scope: scope,
        $rootScope: $rootScope,
        storage: mockStorage,
        User: User
    });
}));

The problem is the mocked storage library in the test is not called by the interceptor, instead it attempts to inject the real one, unsuccessfully, causing an internal error in the library:

TypeError: Cannot read property 'clear' of undefined
    at Object.publicMethods.clearAll (/path/to/app/ui/app/bower_components/ngStorage/src/angularLocalStorage.js:178:12)

Any suggestions? Thanks

1 Answers1

2

See this answer. You'll need to override the real storage service with your mocked one:

  module(function ($provide) {
      $provide.value('storage', mockStorage);
  });
Community
  • 1
  • 1
noj
  • 6,740
  • 1
  • 25
  • 30
  • 2
    That did it, thanks very much. I had to mock up the storage library in a bit more depth, but I added that before each test and the interceptor correctly used the mock version. – Dan Sunderland Jan 14 '15 at 10:12
  • It worked for me as well, but I don't understand why. If storage in this case is a factory, shouldn't it be $provide.factory('storage', mockStorage) ? – Mari Jul 16 '15 at 16:54
  • No because mockStorage is the return value from the original factory, not the factory itself. `$provide.factory` would expect a function with a return value to be used as the injected value. You can see [here](https://github.com/angular/angular.js/blob/071be609275558f177cdb28d8f8891bd1805af1c/src/auto/injector.js#L699) how value wraps the function using [valuefn](https://github.com/angular/angular.js/blob/e1fd333ca4abfeefa4db12b48b6b74732f655f98/src/Angular.js#L469) in a return statement. Hope that makes sense. – noj Jul 17 '15 at 09:20