Testing Angular: What is the best way to test an Angular promise that includes AJAX?

Is it possible to test an angular promise that includes ajax calls?

The scenario involves making an ajax call to a parent, followed by additional ajax calls to its children.

Here is the code snippet:

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;
        });
    };

}]);

A test case for MyController:

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 suggestions on how to improve this test case?

Edit:

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

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

Answer №1

1) Implement the use of setTimeout instead of $timeout in the controller

2) Replace all instances of $httpBackend within the beforeEach function

3) Utilize the .flush() functions for better control

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();

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

it('should showcase how to use when (200 status)', inject(function($httpBackend, $timeout) {

    // var $scope = {}; // Avoid rewriting the scope defined in beforeEach
    $scope.submit();   
    //$httpBackend.flush(); // Wait for the backend to return parents
    $timeout.flush(); // Wait for the timeout in $scope.myParen 
    $httpBackend.flush(); // Wait for the backend to return children

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

});

Answer №2

Please update your $scope.myParen function to utilize $timeout in place 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;
}

For testing purposes, you can use $timeout.flush() to synchronously clear the queue of deferred functions.

Deprecation Notice

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

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

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

When attempting to execute a script that includes document.write, it will not function as expected

Our web program is utilizing ajax and jquery, specifically Netsuite. I've been attempting to adjust elements on a page using document.ready and window.load methods in order to load an external script onto the page. Regardless of whether I load this ex ...

"An error occurs when a function is passed as an argument to another function

I'm facing an issue while attempting to develop a typewriter effect as the func within the typewrite function is turning out to be undefined. const elements = Array.from(document.querySelectorAll("[data-txt]")); typewriteAll(elements, 70); function ...

AngularJS: Potential uncaught rejection encountered with the $http.get() method - Error Status: -1 due to CORS

Encountering an error while using a HTTP GET method: :63502/Scripts/angular.js:14525 Possibly unhandled rejection: {"data":null,"status":-1,"config":{"method":"GET","transformRequest":[null],"transformResponse":[null],"jsonpCallbackParam":"callback","para ...

Unable to populate an array with JSON elements within a for loop triggered by useEffect

In my code, there is an issue with the array candleRealTimeDataQueue not updating correctly. Below is the snippet of the problematic code: let candleCurrentJSONDataWS = null; var candleRealTimeDataQueue = []; let tempDateTime = null; let ca ...

The Google Maps infowindow consistently displays the first infowindow for all other markers, regardless of which marker

I've come across similar questions about this issue, but none of the solutions I found have worked for me. Maybe I'm missing something important. Any assistance would be greatly appreciated. The challenge I'm facing involves a map with ove ...

What could be the reason for my HTML not updating when I add a new value to an array?

Within my HTML document, I have the following piece of code: <div ng-repeat="row in test.user"> .. </div> As part of my code logic, whenever a new record is retrieved, it gets added to an array using the function below: test.addTest = funct ...

Encountered a problem while assigning a function to a variable

I'm currently working with a function that retrieves images based on a search query. Here's the code: function getImage(query){ var serach_title = query.replace(/\ /g, '+'); var imgUrl = "https://ajax.googleapis.com/ajax/s ...

I'm having trouble retrieving the index of the map function within a function called by a button onClick() event, all of which is contained within the scope of the

Initial State const [variationData, setVariationData] = useState([ { name: "", value: [""], }, ]); Function Triggered by Button Click const addValueField = (e, index) => { e.preventDefault(); const fiel ...

Creating interactive rows in a table using AngularJS

I am looking to automatically populate rows in table 2 based on the count value from table 1. Table 1 has a field called count which determines how many rows should be displayed in table 2. I am just starting out with AngularJS, so any guidance on how to ...

Tips on resolving the issue of 'caught local exception thrown' while utilizing instanceof for identifying the type of internal errors

I've encountered a scenario where I'm loading a set of plugins in my code. Each plugin executes a series of functions within a try-catch block to check for failures and handle them accordingly. Some functions within the plugin can return specific ...

Modifying the background-image to an SQL image

Can the background-image: url(--SET THIS--) be set to an SQL picture? I am considering something like this: $img = $MysqliHandler->query(SELECT avatar FROM account WHERE username="'.$_SESSION['name'].'"'; And then somehow cha ...

Is there a way to install angular libraries manually and ensure all dependencies are included without relying on bower or npm?

Currently, I am facing an issue with importing data from an Excel spreadsheet into my Angular application. I came across a helpful guide to import them into UI-Grid using xlsx.js which seems like a good starting point for me. However, my challenge lies in ...

Assign the src value from JavaScript to the img tag

I need assistance on how to properly insert the image source I am receiving into the <img/> tag. JavaScript: var aa = document.getElementById("TableImage"+getid).src; alert(aa); Result of the above JavaScript: Now, I would like to place this link ...

The presence of a default value within an Angular ControlValueAccessor triggers the dirty state due to

My task is to create dynamic Input components for a template driven form using a directive. The default value of the Input component should be set by the component itself. However, I encountered an issue where setting a default value automatically marks t ...

The page's layout becomes disrupted when the back end is activated, requiring some time to realign all controls

I recently set up a website and I am facing an issue where certain pages shake uncontrollably when buttons are clicked. The problematic page can be found at the following link: Specifically, when trying to click on the "SEND OFFER" button, the entire pag ...

Synchronization of Angular functions

How can I properly execute three functions (declared in a service) in sequential order? function1(); function2(); function3(); Each function contains HTTP commands (e.g. PUT or GET), which is causing an issue where Function 3 is executed before Function ...

Implementing a time delay in the jQuery keyup() function following an Ajax request

I am currently facing a complex issue and I am uncertain about the best approach to tackle it. I have multiple textboxes lined up in a row that need to be filled in. Each time a value is entered into a textbox, I make an Ajax call to process that value. De ...

Exploring recommendations using AngularJS

I am currently working on replicating the search suggestion feature found at: where certain words are displayed as you type in the search box. Here is my HTML setup: <form ng-controller="SearchCtrl"> <input name="q" ng-model="query" ng-keyp ...

Unexpected dependency mysteriously appeared in package.json during npm install

I'm in the process of developing a project using npm 7.24.0 and executing npm install --lockfile-version 2 --legacy-peer-deps After the project is built, it adds an additional line to package.json "dependencies": { "2": &quo ...

Script execution in '<URL>' has been prevented due to sandboxing of the document's frame and the absence of the 'allow-scripts' permission setting

When I deploy my pure Angular application with a REST API to a production server and try to access its URL from another site (such as a link in an email), I encounter a strange problem. Firefox doesn't provide any error message, but Chrome says: Blo ...