Using $q.when to simulate AJAX calls through an httpBackend mock with ES6 Promises

When attempting to simulate a response to a JSONP GET request using a function that returns an ES6 promise wrapped in $q.when(), everything seems to be working fine. However, during unit testing, the request isn't intercepted by $httpBackend and goes directly to the actual URL. As a result, when flush() is triggered, an error message saying

Error: No pending request to flush !
is displayed. The JSONP request is performed through jQuery's $.getJSON() within the ES6 promise, prompting the use of a regex instead of a fixed URL to capture all outgoing requests.

I've spent quite some time trying to troubleshoot this issue and can't seem to grasp why the call is bypassing $httpBackend. It feels like the HTTP request within the ES6 promise is being executed "outside of Angular", causing $httpBackend to not recognize or intercept it. This might not have been the case if the call was made within a $q promise from the beginning. Could someone shed light on why this behavior is happening and why a simple timeout workaround works seamlessly? I've experimented with various combinations of $scope.$apply, $scope.$digest, and $httpBackend.flush() without success.

Perhaps reviewing some code snippets will offer clarification...

Controller

function homeController() {
    ...
    var self = this;
    self.getData = function getData() {
        $q.when(user.getUserInformation()).then(function() {
            self.username = user.username;
        });
    };
}

Unit Test

...

beforeEach(module('home'));

describe('Controller', function() {
    var $httpBackend, scope, ctrl;

    beforeEach(inject(function(_$httpBackend_, $rootScope, $componentController) {
        $httpBackend = _$httpBackend_;
        scope = $rootScope.$new(); // attempted to invoke $digest or $apply
        // tried different approaches like whenGET, when('GET', ..), etc...
        $httpBackend.whenJSONP(/.*/)
                    .respond([
                        {
                            "user_information": {
                                "username": "TestUser",
                            }
                        }
                    ]);
        ctrl = $componentController("home");
    }));

    it("should add the username to the controller", function() {
        ctrl.getData(); // trigger HTTP request
        $httpBackend.flush(); // Error: No pending request to flush !
        expect(ctrl.username).toBe("TestUser");
    });
});

...

Strangely enough, the following workaround does work:

    it("should add the username to the controller", function() {
        ctrl.getData(); // trigger HTTP request
        setTimeout(() => {
            // no need to call flush, $digest, or $apply...?
            expect(ctrl.username).toBe("TestUser");
        });
    });

Answer №1

After receiving a helpful comment from Graham, I found myself delving deeper into an unfamiliar territory due to my lack of understanding on certain topics. To prevent others from facing the same confusion, I will summarize the key points here...

  1. My misconception about JSONP led me astray. It operates independently of XmlHttpRequest (as explained here). Instead of struggling with mocking JSONP responses, I disabled JSONP by turning on the "debug" flag in my code. This caused the calls to go through XHR objects instead (which would violate the same origin policy if real external API responses were required).
  2. Instead of utilizing jasmine-ajax, I opted to spy on jQuery's getJSON and provide a mock response. Although this approach successfully passed the mocked response to the ES6 promise, the then function of the $q promise object created from wrapping the ES6 promise was not triggering (nor were any error-handling functions like finally). I attempted to call $scope.$apply() at various points, but it did not yield results.

    Simplified implementation (in unit test):

     ...
     spyOn($, 'getJSON').and.callFake(function (url, success) {
         success({"username": "TestUser"}); // send mock data
     });
     ctrl.getData(); // initiate GET request
     ...
    

    Issue (in controller's source):

     // user.getUserInformation() returns an ES6 promise
     $q.when(user.getUserInformation()).then(function() {
         // The 'then' block was never reached or executed! (during unit tests)
     });
    
  3. In the end, I resorted to the second method mentioned above for sending data and enclosed the assertions in the unit test within a timeout without specifying a time duration. I acknowledge that this workaround is not ideal and ideally should be avoided. Despite investing hours into finding a better solution, I have reached a roadblock and given up. If anyone has suggestions on how to enhance this process or why the then function is not being invoked, I am open to hearing your insights.

    Unit Test:

     ...
     ctrl.getData(); // initiate GET request
     setTimeout(() => {
         expect(ctrl.username).toBe("TestUser"); // successful assertion!
     });
    

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

Guide on dynamically inserting HTML markup written in C# into a .aspx file by utilizing Ajax UpdatePanel's content Template

Below is the code I have in (.Aspx.cs): StringBuilder strMarkup = new StringBuilder(); strMarkup.AppendFormat("<div id=" + "MyDiv" + "><table id= " + "sDetails" + "<tr><td><input id= " + "lblStudentName" + " type=" + "text" + " va ...

Discover the power of Google Maps with Angular. Unlock the full potential of built

I'm currently working on a project involving Google Maps where I need to utilize the setZoom method upon clicking a custom button. The issue I'm encountering is that setZoom() returns undefined. I have loaded all necessary dependencies (the map s ...

The event is eagerly anticipating the AJAX call despite the asynchronous setting being true

I'm implementing a feature where the user can open a new tab to continue using the system while the current tab is busy making a request to the server. To achieve this, I am trying to utilize $(window).load() or $(window.document).ready() to automati ...

Store the fetched data as JSON in the local storage

Struggling to find a solution after reading several articles, I am working on fetching a large JSON from an API and I want to cache the response in the localStorage. The goal is for the script to check for an object with the requested ID in the cached JSON ...

Creating a form in Vue that dynamically generates checkbox options

In my Vue application, I have a dynamically generated form with v-models within a loop. Everything is working correctly except for checkboxes. The issue I am facing is that the v-model is affecting all the loops instead of just one, unlike with the input t ...

Establish a shared authentication state between an application and a widget within an iframe for seamless user login (aka Universal login)

Background: Imagine a web application hosted at service.io where users log in to access various services. Users can also embed a widget on their own websites to provide functionality from the app. Issue: Due to the app and widget residing in different d ...

Guide to setting the radio_button checked for the first child element in Rails 4

Currently, I am working with rails4 and have encountered an issue related to a dropdown list. My goal is to automatically select the radio_button of the first child element when a parent item is chosen from the dropdown menu. Essentially, the dropdown cons ...

Combining various array values into a single key in JSON格式

Issue: I am working on a sign-up form for new users using HTML. The goal is to store multiple arrays (each containing "username: username, password: password, topScore: 0) within one JSON key ("user" key). However, the current functionality only allows f ...

Is it advisable to transpile my Node.js code in order to utilize ES6 features?

My focus in using Node.js is solely for server-side microservices. I'm interested in incorporating ES6 into my code, but I've come across information suggesting that Babel is necessary to transpile the code to ES5 for browser compatibility. My qu ...

Please send a solitary email in accordance with the guidelines provided by Weblesson

I am a newcomer to the world of programming, on a quest to discover how to send a single email rather than bulk emails as demonstrated in the Web lesson tutorial that I came across (link here: 'How to Send Bulk Email in PHP using PHPMailer with Ajax J ...

Encountering a problem with Prerender.io - consistently receiving a '301' miss followed by a '404' error with Nginx and AngularJS

I am currently enhancing the SEO friendliness of my AngularJS e-commerce application and implementing Prerender.io to achieve this goal. For hosting and serving files, I am using nginx from a docker container on AWS. Here are the steps I have taken so fa ...

Tips for sending data from an HTML form to a server in JSON format

In the vast sea of questions with similar titles, I have not found a solution that works for my specific issue. This is why I am reaching out for help by posting this question. My situation revolves around two scenarios: 1) Defining "action" and "method" ...

Dealing with repeated parameters in a URLHow can you handle duplicate

My Ajax select input dynamically changes the URL without refreshing the page. However, I have encountered an issue where repeated parameters stack in the URL when the select input is changed multiple times: [domain]/find.php?cat=1#pricemin=10&pricem ...

REACT: Implement a feature to add a distinctive border around the currently selected image

When selecting a picture, I would like to have a border around it. Specifically, if out of 6 pictures I choose 3, I want highlighted borders around those selected images. How can this be achieved? EDIT: I am utilizing React to address this issue. ...

Local working fine but encountering issues on Openshift, specifically with syncing npm package versions across environments (local and global)

As I develop a forum using Angular that connects with Node/Mongo, there are numerous requests required to populate the page with necessary data. While everything functions flawlessly locally, once uploaded to Openshift, the site displays various bugs and f ...

The use of Buffer() is no longer recommended due to concerns regarding both security vulnerabilities and

I'm encountering an issue while trying to run a Discord bot. The code I'm using involves Buffer and it keeps generating errors specifically with this code snippet: const app = express(); app.get("/", (req,res) => { if((new Buffer(req.quer ...

Display map upon clicking on Google Map marker trigger an AJAX request

Hello, I'm facing an issue with creating a new map when clicking on a marker. Here is the process I want to achieve: Show the default google map with included markers - this part works fine When I click on a marker, I want to create a new map where ...

json - {"response":{"messageTitle":"Message Delivered"}}

I'm new to using ajax, but it seems like the solution to my issue. I have a PHPmailer form on indk.org/contact.html that opens a new window displaying {"success":{"title":"Message Sent"}} when you hit submit. How can I prevent this from happening or ...

Top method for triggering an action on the client-side during Sign In with the help of Redux, React, and NextAuth

Currently, I am developing a web application that utilizes the Spotify API. My goal is to seamlessly load the user's playlists as soon as they log in using NextAuth. At the moment, there is a button implemented to trigger playlist loading, but it onl ...

best practices for sending multiple requests using node js

I have a list of 4 URLs, each containing JSON data. How can I iterate through these URLs? Example: URLs = [ "", "", "", ""]; Each URL contains JSON data for one student as follows: { date: 08/05/2014 stude ...