如何在测试期间避免$ digest正在进行中

时间:2014-03-06 10:43:34

标签: javascript angularjs google-maps unit-testing

我正在测试使用Google Maps Geoencoding服务的服务。认为它很容易,因为它是非常简单的代码。但似乎不是。

这是服务:

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

为了不使用真实的google.maps我正在嘲笑GoogleAPILoaderService和google.maps

然而,当我尝试测试它时,我进入了$digest already in progress。我尝试使用safeApply,但无效。

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

这是完整的规范:

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

常见问题

  • 您是否尝试过$$phase检查?

是。不行。不知何故,此时相位为空。我担心通过致电$apply我会释放其中两个,这是导致问题的原因。

  • 你能为此提供一个Plunker吗?

当然可以!链接到Plunker

1 个答案:

答案 0 :(得分:4)

问题很简单。不需要$apply内的mapsReadyPromise,因此当您在测试中执行另一个$apply时,它会变得疯狂。删除$apply解决了$digest问题,然后您只需解决几个问题并准备就绪:)

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