Question

I'm learning unit-testing in angularjs and i'm trying to test my authentication controller.

Currently the test is failing with Expected Function to equal '/dashboard'. The test does not seem to be getting into User.login from what I can tell.

Controller:

angular.module('foo').controller('LoginCtrl',function($scope, $rootScope, $http, $window, $location, User){

 $scope.submit = function () {

      User.login($scope.user,

          function (user) {    // Success
            $window.sessionStorage.token = user.id;
            $scope.message = 'Welcome';
            // Redirect to dashboard
            $location.path('/dashboard');
          },
          function (err) {
            console.log(err);

            // handle login errors
            $scope.message = 'Error: Invalid user or password';
          }
      );
    };
});

Test:

describe('LoginCtrl', function() {

    beforeEach(module('foo'));

    var scope, ctrl, location, window, user;

  beforeEach(inject(function($rootScope, $controller, $location, $window, User) {
    scope = $rootScope.$new();
    location = $location;
    window = $window;
    user = User;
    ctrl = $controller('LoginCtrl', {$scope: scope, User: user});
  }));

  it('should redirect upon successful login', function() {
    console.log('before path = '+location.path());
    scope.user = {
      "username": "my_user",
      "password": "my_pass"
    };
    scope.submit();
    console.log('message = '+scope.message);
    console.log('after path = '+location.path());
    console.log(window.sessionStorage.getItem('token'));
    expect(location.path).toEqual('/dashboard');
  });

});

** EDIT **

User.login code:

module.factory(
  "User",
  ['LoopBackResource', 'LoopBackAuth', '$injector', function(Resource, LoopBackAuth, $injector) {
    var R = Resource(
      urlBase + "/users/:id",
      { 'id': '@id' },
      {

      "login": {
          url: urlBase + "/users/login",
          method: "POST",
          interceptor: {
            response: function(response) {
              var accessToken = response.data;
              LoopBackAuth.currentUserId = accessToken.userId;
              LoopBackAuth.accessTokenId = accessToken.id;
              LoopBackAuth.rememberMe = response.config.params.rememberMe !== false;
              LoopBackAuth.save();
              return response.resource;
            }
          }
        }
});
Was it helpful?

Solution

Your User.login function must be calling the callback method asynchronously, so when you call scope.submit();, your callback function is not called yet => the test fails.

To test this logic, you have to mock the User.login function:

 it('should redirect upon successful login', function() {
    console.log('before path = '+location.path());
    scope.user = {
      "username": "my_user",
      "password": "my_pass"
    };

    //user here is user = User; in your beforeEach. I avoid pasting too much code.
    //Jasmine 1.3: andCallFake
    //Jasmine 2.0: and.callFake
    spyOn(user, "login").andCallFake(function(userData,successCallback){
         successCallback(userData); //simulate the success case.
    }); //mock your User.login

    scope.submit();
    console.log('message = '+scope.message);
    console.log('after path = '+location.path());
    console.log(window.sessionStorage.getItem('token'));
    expect(location.path()).toEqual('/dashboard'); //fix the problem with location.path
 });

Explanation:

spyOn(user, "login").andCallFake replaces the actual function with our fake function.

In this test case, you're testing should redirect upon successful login, so the precondition is the login must be successful, by mocking the login function, we can ensure this precondition is always true in the test.

You could do this similarly to test a case like: set error message when login failed, in order to test this, you need to ensure the precondition login failed is always true in the test:

it('should redirect upon successful login', function() {
    console.log('before path = '+location.path());
    scope.user = {
      "username": "my_user",
      "password": "my_pass"
    };

    //user here is user = User; in your beforeEach. I avoid pasting too much code.
    //Jasmine 1.3: andCallFake
    //Jasmine 2.0: and.callFake
    spyOn(user, "login").andCallFake(function(userData,successCallback,errorCallback){
         errorCallback(userData); //simulate the error case.
    }); //mock your User.login

    scope.submit();

    expect(scope.message).toEqual('Error: Invalid user or password'); //verify that the message was set correctly.
 });

OTHER TIPS

Expected Function to equal '/dashboard'

The test runner is telling you it expected a string '/dashboard', but instead got a reference to a function. That's because location.path is a reference to a function. Try this instead:

expect(location.path()).toEqual('/dashboard');
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top