Tips for preventing $digest cycle already in progress error while testing

I've been struggling to test a service that utilizes the Google Maps Geocoding service. I initially thought it would be simple since the code is pretty straightforward, but it's proving to be more complex than expected.

Here's an overview of the service:

(function () {
    'use strict';

    var GoogleGeocodingService = function ($q, GoogleAPILoaderService, $rootScope) {

        var geocoder,
            mapsReadyPromise;

        mapsReadyPromise = GoogleAPILoaderService.load('maps', '3', {other_params: 'sensor=false'}).then(function() {
            geocoder = new google.maps.Geocoder();
        });

        var getLatLng = function (searchKeyword) {
            var deferred = $q.defer();

            mapsReadyPromise.then(function () {
                geocoder.geocode({'address': searchKeyword}, function (results, status) {
                    $rootScope.$apply(function () {
                        if (status === google.maps.GeocoderStatus.OK) {
                            deferred.resolve(results);
                        } else {
                            deferred.reject(status);
                        }
                    });
                });
            });

            return deferred.promise;
        };

        return {
            getLatLng: getLatLng
        };

    };

    app.factory('GoogleGeocodingService', ['$q', 'GoogleAPILoaderService', '$rootScope', GoogleGeocodingService]);
}());

To avoid using the real google.maps, I've mocked both the GoogleAPILoaderService and google.maps.

However, when attempting to run tests, I encounter the $digest already in progress error. I've experimented with safeApply without success.

it('Should call geocoder.geocode to retrieve results', function () {
    GoogleGeocoding.getLatLng('Canada');
    $rootScope.$apply();
    expect(GeoCoderMock.prototype.geocode).toHaveBeenCalledWith({ address : 'Canada'});
});

This is the complete specification:

(function () {
    "use strict";
    var GeoCodingOK, GeoCodingError, GeoCoderMock, GoogleAPILoaderMock, $rootScope, $q, $timeout, GoogleGeocoding;

    describe('Google Geocoding Service', function () {

        beforeEach(angular.mock.module('app', function($provide){
            GoogleAPILoaderMock = jasmine.createSpyObj('GoogleAPILoaderService',['load']);
            $provide.value('GoogleAPILoaderService',GoogleAPILoaderMock);
        }));

        beforeEach(inject(function (_$q_,_$rootScope_) {
            $q = _$q_;
            $rootScope = _$rootScope_;

            GoogleAPILoaderMock.load.andCallFake(function () {
                var deferred = $q.defer();
                deferred.resolve('Library Loaded');             
                return deferred.promise;
            });
        }));

        beforeEach(inject(function (GoogleGeocodingService) {
            GoogleGeocoding = GoogleGeocodingService;

            window.google = jasmine.createSpy('google');
            window.google.maps = jasmine.createSpy('maps');
            window.google.maps.GeocoderStatus = jasmine.createSpy('GeocoderStatus');
            window.google.maps.GeocoderStatus.OK = 'OK';

            GeoCodingOK = function (params, callback) {
                callback({data: 'Fake'}, 'OK');
            };

            GeoCodingError = function (params, callback) {
                callback({data: 'Fake'}, 'ERROR');
            };

            GeoCoderMock = window.google.maps.Geocoder = jasmine.createSpy('Geocoder');
            GeoCoderMock.prototype.geocode = jasmine.createSpy('geocode').andCallFake(GeoCodingOK);
        }));

        it('Should expose some functions', function(){
            expect(typeof GoogleGeocoding.getLatLng).toBe('function');
        });
        describe('getLatLng function', function () {
            it('Shouldn\'t call anything if the promise hasn\'t been resolved', function () {
                GoogleGeocoding.getLatLng('Canada');
                expect(GeoCoderMock.prototype.geocode).not.toHaveBeenCalled();
            });
            it('Should return a promise', function () {
                var promise = GoogleGeocoding.getLatLng('Canada');
                expect(typeof promise.then).toBe('function');
            });
            it('Should call geocoder.geocode to retrieve results', function () {
                GoogleGeocoding.getLatLng('Canada');
                $rootScope.$apply();
                expect(GeoCoderMock.prototype.geocode).toHaveBeenCalledWith({ address : 'Canada'});
            });
            it('Should resolve the promise when receiving data', function () {
                var okMock = jasmine.createSpy();
                GoogleGeocoding.getLatLng('Canada').then(okMock);
                $rootScope.$apply();
                expect(okMock).toHaveBeenCalledWith({ address : 'Canada'});
            });
        });
    });
}());

Frequently Asked Questions (FAQ):

  • Have you tried checking $$phase?

Yes, I have checked it. Unfortunately, it doesn't work. It seems that the phase is null at that point. I suspect that by calling $apply, I might be triggering two phases simultaneously, leading to the issue.

  • Can you share a Plunker demonstrating this issue?

Sure, here is the link to the Plunker

Answer №1

Sometimes simplicity is key. The unnecessary $apply within the mapsReadyPromise was causing chaos when another $apply was triggered during the test. Once that redundant $apply was removed, the issue with $digest was resolved, requiring only a few minor fixes to get everything back on track :)

http://plnkr.co/edit/xyz123Abc456defGhi?p=preview

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

How can I retrieve the position of a div or an image within a div (specifically the top and left coordinates) and incorporate these values into CSS?

I'm currently working on a website that is dynamic and utilizes bootstrap. I am looking to incorporate an animation where the dynamic thumbnails in the col-md-4 div zoom to 100% when clicked, appearing at the center of the container. I am struggling ...

Troubleshooting Angular 2 ng-if issues

Is there a way to dynamically apply CSS styles to an element that has an ng-if condition, even if the condition fails? It currently works fine when the condition is true, but I'm looking for a solution that allows me to modify the element regardless o ...

What is the best way to transfer XML data format from a web browser to a server using jQuery?

Is there a method in jQuery to transmit data to the server from a browser in XML format? Thank you, Sana. ...

Struggling with linking my Angular Controller with my View and experiencing difficulty establishing a connection

I'm encountering an issue while attempting to link a controller to my view. The error I keep receiving is as follows: Error: ng:areq Bad Argument Argument 'TestAppCtrl' isn't a function, received undefined Here's the content ...

Angular $watch | obtaining the result from a function

I'm curious why I consistently have to use this $scope.$watch( function() { return $scope.someData; }, function( value ) { console.log( value ); }); in Angular in order for it to watch the data. It's frustrating to me because it seems un ...

When I use AJAX to load a PHP file, the function's content returns as [object HTMLDivElement]

Hello there, When I use AJAX to load a PHP file, the content of my function returns [object HTMLDivElement], but if I load my function without loading the PHP file, it displays normally. index.php <h1>API Football</h1> <nav> ...

Interactive Google Maps using Autocomplete Search Bar

How can I create a dynamic Google map based on Autocomplete Input? Here is the code that I have written: <script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDeAtURNzEX26_mLTUlFXYEWW11ZdlYECM&libraries=places&language=en"></scri ...

Reloading nested Iframe with Selenium automation script

Currently, I am facing a challenge while working on an application that contains nested Iframes within the user interface. Some of these Iframes undergo refreshing during the test execution process. Is there any approach that allows us to simulate the refr ...

Link HTMLMediaElement with Android's playback controls

For my HTML/Javascript audio player that supports playlists, I have implemented a feature where the ended event automatically plays the next media in line. Everything works smoothly when using the player on Android Bromite browser, including the playback c ...

Elevate the React experience by smoothly sliding a fixed navbar upwards when scrolling down, and elegantly sliding it

tl;dr Don't miss the solution at the end! Looking to create a slide up and down effect on a fixed navbar in react? What's the best approach - using refs or the componentDidMount lifecycle hook? hideNav = (navbar) => { const hide = () ...

What is the best method for implementing a file upload feature using jQuery and php?

Could someone explain how to create a jQuery multiple image upload feature (uploading without refreshing the page after choosing a file, only displaying the image but not inserting it into a database), and submit additional form data along with all images ...

Encountering an issue with Server Side Rendering in React Router Dom where an error message pops up saying: "Warning: React.createElement: type is

Specific Error: A warning has occurred: React.createElement: the type provided is invalid -- it was expecting a string (for built-in components) or a class/function (for composite components), but instead received an object. in Posts in Connect(Po ...

At what point is the JavaScript function expression triggered in this code snippet?

let express = require('express') let app = express(); app.use(express.static('static')); let server = app.listen(3000, function() { let port = server.address().port; console.log("The server has started on port", port); }); I ...

The upload directory fails to include the folder name when sending a file

After experimenting with the new directory upload feature, I encountered an issue where the server request did not preserve the exact folder structure as expected. Here is the HTML code snippet: <form action="http://localhost:3000/" method="post" enct ...

Unable to close Bootstrap modal upon clicking "x" or "close" buttons

Hey everyone, I'm having a bit of trouble with my modal. It appears correctly when clicked to open, and the close buttons seem to detect that my mouse is hovering over them. However, when I click on the buttons, nothing happens and the modal remains o ...

Make sure to place the <i> tag within the <li> tag at the bottom to automatically resize the height

Currently, I am working on customizing my menu design with a mix of bootstrap and font awesome CSS styles. It would be easier to demonstrate the issue on a live page. My main objectives are two-fold: I want to position the chevron icon at the bottom with ...

Discover instances of a string within an array using JQuery

I am currently exploring how to locate occurrences of a specific string within an array on the client side. The examples provided on the JQuery Docs all seem focused on number comparisons, which isn't quite what I need. Essentially, I'm attempti ...

Tips for translating an HTML webpage from Arabic to English

I have a bootstrap site with HTML pages but no backend functionality. How can I manually translate from Arabic to English, given that I already have the translations for all content and don't need to rely on translation tools? Is there a way to map Ar ...

Using Props with jQuery in React Components: A Comprehensive Guide

I trust you comprehend this straightforward example. I attempted to modify the background color of my HTML element during initial rendering by managing it in a React Component with a touch of jQuery assistance. Here is the code within my React Component ...

Locating content inside a span element with tags and spacing in jquery

Within the span class of teamName, there are various tags including white-spacing, a sup tag, and a p tag: <span class="teamName"> Dr.<sup>J</sup> W Smith ...