Run
Run

Reputation: 57226

Angular test: How can I test angular promise with ajax in it?

How can I test angular promise with ajax in it?

The code is to call a parent via ajax then calling the resting of its children via ajax too.

Code,

app.controller('MyController', ['$scope', '$http', '$timeout', '$q', function($scope, $http, $timeout, $q) {

    $scope.myParen = function(url) {
        var deferred = $q.defer();

        setTimeout(function() {
        $http({
            method: 'GET',
            url: url
        })
        .success(function(data, status, headers, config) {
            deferred.resolve([data]);
        })
        .error(function(data, status, headers, config) {
            deferred.reject(data);
        });
        }, 1000);

        return deferred.promise;
    }

    $scope.submit = function() {
        $scope.commentCollection = '';

        var promise = $scope.myParen('https://example.com/parents/1');

        promise.then(function(success) {
            var list = success;

            $http({
                method: 'GET',
                url: 'https://example.com/parents/1/children'
            })
            .success(function(data, status, headers, config) {
                $scope.commentCollection = list.concat(data);
            })
            .error(function(data, status, headers, config) {
                $scope.error = data;
            });
        }, function(error) {
            $scope.error = error;
        });
    };

}]);

Test,

describe('MyController Test', function() {

    beforeEach(module('RepoApp'));

    var controller, $scope, $http, $httpBackend, $q;
    var deferred;

    beforeEach(inject(function ($rootScope, $controller, $http, $httpBackend, $q) {

        $scope = $rootScope.$new();

        deferred = $q.defer();

        // Create the controller.
        controller = $controller;
        controller("MyController", {$scope, $http, $httpBackend, $q});
    }));

    it('should demonstrate using when (200 status)', inject(function($rootScope, $http, $httpBackend, $q) {

        var $scope = {};

        /* Code Under Test */
        $scope.myParen = function(url) {
            ...
        }

        $scope.submit = function() {
            ...
        };
        /* End */

        $scope.submit();

        deferred.promise.then(function (value) {

            $httpBackend.whenGET('https://example.com/parents/1/children', undefined, {})
            .respond(function(){ return [200,{foo: 'bar'}]});

            expect(value).toBe(4);
        });
        deferred.resolve(4);
        $rootScope.$apply();

        expect($scope.commentCollection).toEqual({foo: 'bar'});
    }));
});

Failed result,

Expected '' to equal { foo: 'bar' }.

Any ideas?

Edit:

....
deferred.resolve(4);
$rootScope.$apply();

$timeout.flush();
expect($scope.commentCollection).toEqual({foo: 'bar'});

Upvotes: 1

Views: 104

Answers (2)

vorant
vorant

Reputation: 708

1) switch setTimeout on $timeout in controller

2) replace all $httpBackend in beforeEach function

3) use .flush() functions

describe('MyController Test', function() {

beforeEach(module('app'));

var controller, $scope, $http, httpBackend, $q;
var deferred;

beforeEach(inject(function ($rootScope, $controller, $http, $httpBackend, $q) {

    $httpBackend
        .whenGET('https://example.com/parents/1', undefined, {})
        .respond(function(){ return [200, {parents: []}]});

    $httpBackend
        .whenGET('https://example.com/parents/1/children', undefined, {})
        .respond(function(){ return [200, {foo: 'bar'}]});

    $scope = $rootScope.$new();

    deferred = $q.defer();

    // Create the controller.
    controller = $controller;
    controller("MyController", {$scope: $scope, $http: $http, $httpBackend: $httpBackend, $q: $q});
}));

it('should demonstrate using when (200 status)', inject(function($httpBackend, $timeout) {

    // var $scope = {}; // don't write it, becouse you rewrite a scope which defined at beforeEach function
    $scope.submit();   
    //$httpBackend.flush(); // wait when backend return parents
    $timeout.flush(); // wait timeout in $scope.myParen 
    $httpBackend.flush(); // wait when backend return children

    expect($scope.commentCollection[0]).toEqual({parents: []});
    expect($scope.commentCollection[1]).toEqual({foo: 'bar'});
}));

});

Upvotes: 2

georgeawg
georgeawg

Reputation: 48968

Re-write your $scope.myParen function to use $timeout instead of setTimeout.

$scope.myParen = function(url) {

    var promise = $http({
        method: 'GET',
        url: url
    })
    .then (function(response) {
        var data = response.data;
        return $timeout(function(){return data;}, 1000);
    })
    .catch(function(response) {
        var data = response.data;
        return 
            ($timeout(angular.noop, 1000)
            ).then (function () {
                 throw data;
            });
    });

    return promise;
}

Then in tests you can use $timeout.flush() to synchronously flush the queue of deferred functions.

Deprecation Notice

The $http legacy promise methods success and error have been deprecated. Use the standard then method instead.

-- AngularJS $http Service API Reference -- deprecation notice

Upvotes: 1

Related Questions