2

I have an Angular directive that, when attached to an <input>, waits one second after user input before querying an endpoint with $http. In short, it's meant to check username uniqueness.

It looks like this:

.directive('sgValidUsername', ['$http', function(http) {
    var waitTimer;
    var checkIfUserExists = function(e, ctrl) {
            http.get('/publicapi/users/' + e.target.value + '/exists')
                .success(function(data) {
                    ctrl.$setValidity('unique', !data.exists);
            });
        };

    return {
        restrict: 'A',
        require: 'ngModel',
        link: function(scope, element, attrs, ctrl) {
            element.on('blur keyup', function(e) {
                if (e.target.value) {
                    clearInterval(waitTimer);
                    waitTimer = setTimeout(function() {
                        checkIfUserExists(e, ctrl);
                    }, 1000);
                }
            });
        }
    };
}]);

I'm trying my best to write a good comprehensive Jasmine unit test suite, but it's not working out because I couldn't find an appropriate example to learn from. I end up reconstructing the directive in test form rather than actually testing the directive. Also, I get a 'no pending request to flush' error.

Any suggestions for the below?

describe('SignupForm directives', function () { 
    var form,       // el to which directive is applied
    $scope,
    $httpBackend, // $http mock
    usernameExistsHandler;

beforeEach(function() {
    module('signupform.directives');
});

beforeEach(function() {
    inject(function ($injector, $rootScope, $compile, $q, $timeout) {
        $scope = $rootScope;
        $httpBackend = $injector.get('$httpBackend');
        usernameExistsHandler = $httpBackend.whenGET(/\/publicapi\/users\/.+?\/exists/);
        var el = angular.element('<form name="form"><input type="text" name="username" ng-model="user.username" sg-username-is-valid /></form>');

        $scope.user = { username: null };
        $compile(el)($scope);
        form = $scope.form;
    });
});

afterEach(function() {
 $httpBackend.verifyNoOutstandingExpectation();
 $httpBackend.verifyNoOutstandingRequest();
   });

   it('should invalidate with existing usernames', function() {
    form.username.$setViewValue('username_in_use');

    $scope.$digest();

    expect($scope.user.username).toEqual('username_in_use');

    usernameExistsHandler.respond('200', { exists: true });
    $httpBackend.expectGET('/publicapi/users/' + $scope.user.username + '/exists/');
    $httpBackend.flush();

    expect(form.username.$valid).toBe(false);
   });
3
  • 3
    Did you think about moving the $http call into a service/factory and just spying on the call to the factory instead of using the $http directly in your directive? You won't have to mix the $http service testing and directive testing than. Commented Oct 21, 2014 at 6:49
  • @m.brand: I did but it didn't seem appropriate because neither this directive nor the proposed service were likely to be reused elsewhere. I can certainly return to that if there's no better solution but at the moment it just seems like extra project clutter and overdesign. Commented Oct 21, 2014 at 12:55
  • 1
    Well, your files are going to be combined/minified in the end anyways, so I would really go with splitting them up. It's always easier to test small units. Commented Oct 21, 2014 at 12:58

0

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.