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