3

Here is part of my controller:

    $scope.isLoggedIn = function () {
        return Boolean($sessionStorage.isLoggedIn);
    };

    $scope.login = function () {
        //If the browser fills in the username and password field then angular's model will not be updated so we need
        //to manually pull them out of the DOM :(. See this issue for details: https://github.com/angular/angular.js/issues/1460
        var username = $('#usernameInput').val();
        var password = $('#passwordInput').val();
        $scope.loginErrMsg = null;

        $http.jsonp("http://ldap-auth.otpp.me" + '/login?callback=JSON_CALLBACK&username=' + encodeURIComponent(username) + '&password=' + encodeURIComponent(password))
            .success(function (data, isLoggedIn) {
                // This app is for internal use only, security is not a major concern.
                // So using sessionStorage to store the login status is OK.
                $sessionStorage.displayName = data.displayName;
                $sessionStorage.isLoggedIn = true;
            })
            .error(function () {
                $scope.loginErrMsg = "Invalid username or password";
            });
    };

Here is my test:

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

// load the controller's module
beforeEach(module('uiappApp'));

var MainCtrl,
    $httpBackend,
    scope;

// Initialize the controller and a mock scope
beforeEach(inject(function ($injector, $controller, $rootScope) {
    scope = $rootScope.$new();
    MainCtrl = $controller('MainCtrl', {
        $scope: scope
    });
    $httpBackend = $injector.get('$httpBackend');

}));
it('should login', function () {
    var isLoggedIn = scope.isLoggedIn();
    expect(isLoggedIn).toBe(false);

    var username = $('<input type="text" id="usernameInput"/>');
    var password = $('<input type="text" id="passwordInput"/>');
    $('body').html('<div>')
        .find('div')
        .append(username)
        .append(password);
    username.val('username');
    password.val('password');
    var response =  $httpBackend.expectJSONP('http://ldap-auth.otpp.me/login?callback=JSON_CALLBACK&username=username&password=password');
    response.respond({displayName: 'userX'}, {'A-Token': 'xxx'});
    scope.login();
    expect(scope.isLoggedIn()).toEqual(true);
    $('body').empty();
    });
});

However the test always fails (Expected false to equal true.). It seems that I didn't get $httpBackend working. But I cannot figure out where was wrong. Basically I want to mock $http.jsonp() response to provide a mocked-up json when unit testing and then test the user login.

Thanks very much if you can help!

@HackedByChinese After folloing your step, I have:

it('should login', function () {
    var isLoggedIn = scope.isLoggedIn();
    expect(isLoggedIn).toBe(false);

    var username = $('<input type="text" id="usernameInput"/>');
    var password = $('<input type="text" id="passwordInput"/>');
    $('body').html('<div>')
        .find('div')
        .append(username)
        .append(password);
    username.val('username');
    password.val('password');
    var response =  $httpBackend.expectJSONP('http://ldap-auth.otpp.me/login?callback=JSON_CALLBACK&username=username&password=password');
    response.respond({displayName: 'userX'}, {'A-Token': 'xxx'});
    scope.login();
    $httpBackend.flush();
    expect(scope.isLoggedIn()).toEqual(true);
    $('body').empty();
});

But I got the following error message:

    Error: Unexpected request: GET views/main.html
No more request expected
    at $httpBackend (C:/repo/docserver-ui/UIApp/app/bower_components/angular-mocks/angular-mocks.js:1177:9)
    at sendReq (C:/repo/docserver-ui/UIApp/app/bower_components/angular/angular.js:7967:9)
    at $get.serverRequest (C:/repo/docserver-ui/UIApp/app/bower_components/angular/angular.js:7708:16)
    at deferred.promise.then.wrappedCallback (C:/repo/docserver-ui/UIApp/app/bower_components/angular/angular.js:11100:81)
    at deferred.promise.then.wrappedCallback (C:/repo/docserver-ui/UIApp/app/bower_components/angular/angular.js:11100:81)
    at C:/repo/docserver-ui/UIApp/app/bower_components/angular/angular.js:11186:26
    at Scope.$get.Scope.$eval (C:/repo/docserver-ui/UIApp/app/bower_components/angular/angular.js:12175:28)
    at Scope.$get.Scope.$digest (C:/repo/docserver-ui/UIApp/app/bower_components/angular/angular.js:12004:31)
    at Function.$httpBackend.flush (C:/repo/docserver-ui/UIApp/app/bower_components/angular-mocks/angular-mocks.js:1435:16)
    at null.<anonymous> (C:/repo/docserver-ui/UIApp/test/spec/controllers/main.js:43:22)
Process finished with exit code 0

And here is my partial html:

            <form class="navbar-form navbar-right" role="form" ng-show="!isLoggedIn()">
                <p class="navbar-text" id="loginErrMsg">{{loginErrMsg}}</p>
                <div class="form-group">
                    <input type="text" placeholder="User Name" id="usernameInput"
                           class="form-control"
                           ng-class="{'error-fields': loginErrMsg}">
                </div>
                <div class="form-group">
                    <input type="password" placeholder="Password" id="passwordInput" class="form-control" ng-class="{'error-fields': loginErrMsg}">
                </div>
                <button type="submit" class="btn btn-success" ng-click="login()">Sign in</button>
        </form>

1 Answer 1

4

After calling scope.login() and before your expect(...), you need to call $httpBackend.flush() for it to respond with the specified expectations.

This is not specific to JSONP, but unit testing $http calls in general.

To summarize, here are the steps to unit test code that makes $http requests:

  1. Set up your expectations using $httpBackend.
  2. Invoke the code that makes the $http call(s).
  3. Call $httpBackend.flush() to make it respond to any pending requests with the expectations you set up.
  4. Make your test assertions.

See the docs regarding flush.

Sign up to request clarification or add additional context in comments.

5 Comments

Thanks for your reply! But I still got error message: Error: Unexpected request: GET views/main.html. Details in my refined question. Thanks!
I added $httpBackend.when('GET', 'views/main.html').respond(); then the problem is solved. But I'm not sure if this is just a random hack or the correct way to solve the problem.
Is main.html part of your application? Is there code omitted from your question that later loads a template or tries to redirect to another route? If so, you are probably experiencing problems because you are mixing unit testing and end-to-end testing. While they appear similar, they have different purposes for good reason. Angular has recommendations for dedicated to both unit and E2E testing.
Yes, I have $route.reload() in $scope.logout(). Thanks very much for the clarification!
FYI, you could create a fake $route and add it as another property on the second parameter you pass to $controller(...) (where you specify $scope: $scope; same thing but with $route). If you create a spy on $route.reload, you can then test that it was called. This will ensure the functionality works without extra setup.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.