4

I've only done a small bit of testing in AngularJS using Jasmine and Karma (test runner) but I am stuck at the moment. I am integrating google maps on a partial view and I am eager to test the functions I wrote for It in the corresponding controller (placing a marker when a click happens, changing the radius of a circle on the map,...)

All the functionality works but testing it seems a lot harder. This is a simple test that I am using to verify that a service is defined:

  it('should contain a locationDbService',
    function () {
        expect(locationDbService).toBeDefined();
    });

I have the following code executed before each test:

var ctrl, scope, locationDbService;
// inject the $controller and $rootScope services
// in the beforeEach block
beforeEach(inject(function ($controller, $rootScope, _LocationDbService_) {
    // Create a new scope that's a child of the $rootScope
    scope = $rootScope.$new();
    // Create the controller
    ctrl = $controller('AddLocationCtrl', {
        $scope: scope,
        LocationDbService: _LocationDbService_
    });
    locationDbService = _LocationDbService_;
}));

The controller header is the following:

.controller('AddLocationCtrl', function ($scope, LocationDbService) {

Initialize function in the controller:

    var map;

    $scope.initialize = function () {
        var mapOptions = {
            zoom: 15,
            center: new google.maps.LatLng(51.142036, 4.440966)
        };

        map = new google.maps.Map(document.getElementById('map-canvas-add-location'), mapOptions);

    };

    google.maps.event.addDomListener(window, "load", $scope.initialize());

View:

            <div class="item item-body">
                <div id="map-canvas-add-location"></div>
            </div>

The problem I encountered at the start was:

Google not defined

I "fixed" it by added the google maps js file to the Karma conf files array, is this a correct solution?

When I add the js file and run the test again I get the following failure:

offsetWidth null

I've been searching the web for a solution for many hours now and can't find any of them to be working. What am I doing wrong?

Thanks in advance!

2
  • That error is typically shown when there is no div element with the same id. In your case map-canvas-add-location. Commented Apr 8, 2014 at 11:32
  • 1
    I have an element with the ID thats why it works when I just test it in the browser. Commented Apr 8, 2014 at 11:36

1 Answer 1

2

For your first error screen, you are correct that it is due to the jasmine test runner not containing a reference to the google maps js file.

For the second error you do not have a div element with map-canvas-add-location. Your view does contain the div element, but it isn't being loaded within your jasmine test suite. This is correct behavior, unit tests should not depend on a specific dom structure as it makes them far more brittle.

I know of 2 ways to handle the second error:

  1. Utilizing services to wrap specific calls to google map dependencies. This is how my current angular maps project works.
  2. Converting all map level tests to E2E tests instead of unit tests.

I recommend number 1 as E2E testing only is not a step to be taken lightly. E2E tests are generally more brittle and harder to maintain.

Here is a sample of how a wrapper service might look:

.factory('GoogleMap', function(){
    return {
        createMap: function(element, options){
            return new google.maps.Map(element, options);
        },
        mapOptions: {
            animation: google.maps.MapTypeId.DROP,
            maxZoom: 15,
            mapTypeControlOptions: {
                position: google.maps.ControlPosition.LEFT_BOTTOM,
                style: google.maps.MapTypeControlStyle.DROPDOWN_MENU
            }
        },
        markerOptions:{
            animation: google.maps.Animation.DROP,
            clickable: true
        },
        position: function(location){
            return new google.maps.LatLng(location.latitude, location.longitude);
        },
        geocode: function(search, successFun){
            var geocoder = new google.maps.Geocoder();
            geocoder.geocode(search, successFun);
        },
        bounds: function(){
            return new google.maps.LatLngBounds();
        }
    };
});

and used within code:

var mapOptions = angular.extend({
        zoom: 15,
        center: GoogleMap.position({latitude: 51.142036, longitude: 4.440966})
    }, GoogleMap.mapOptions);
map = GoogleMap.createMap(document.getElementById('map-canvas-add-location'), mapOptions);

Then in your tests you'll need to create a mock GoogleMap service and setups spies on that mock to assert your map is being correctly generated.

Here is a sample mock GoogleMap service:

.factory('mockGoogleMap', function(){
    var mapResult = {
        formatted_address: 'mock formatted address',
        geometry: {
            location: {
                lat: function(){
                    return 1;
                },
                lng: function(){
                    return 2;
                }
            }
        }
    };
    return {
        createMap: function(element, options){
            return {
                //add functions called by your controller on the map here
            };
        },
        mapOptions: {},
        markerOptions: {},
        position: function(location){
            return {latitude: location.latitude, longitude: location.longitude};
        },
        geocode: function(params, success){
            success([mapResult], null);
        },
        geocodeResult: mapResult,
        bounds: function(){
            return {
                extend: function(position){},
                getCenter: function(){ return {}; }
            };
        },
    };
});
Sign up to request clarification or add additional context in comments.

3 Comments

Thank you for this very detailed answer! The only thing that seems a bit odd to me is the angular.extend function, I've read the documentation but I don't really get it, do you mind explaing its function?.
I'd be happy to :). angular.extend is used to make a shallow copy of one object's properties onto another object. i.e. a = {b: 'c'}; b = angular.extend({c: 'd'}, a); will yield b equal to {c: 'd', b: 'c'}. read more here: stackoverflow.com/questions/16797659/…
Additionally in this case angular.extend avoids a thorny bug. If instead you did var options = GoogleMap.mapOptions; options.zoom = 15; then all future GoogleMap.mapOptions would also get zoom set to 15. Additionally 2 maps could fight over the property defaults if they were both loaded at once.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.