1

Let's say you have an Angular app that's showing a list of places. There is a button to get your current location, and clicking the button orders the list according to distance from your location, nearest first.

To test this in Protractor you want to be able to click the button and inspect the list:

it('Should order items according to distance', function () {
    locButton.click();
    expect(...).toBe(...); // Check that the first item on the list
                           // the closest to the given lat/long

});

Now, let's say the button calls a method in a controller, the controller calls a method in a service, and the service calls navigator.geolocation.getCurrentPosition() (and, for good measure, that call is wrapped in a promise). The best way to test this is to mock the call to getCurrentPosition() and return a specific latitude and longitude, so that has the required effects all the way back up the chain to the page output. How do you set that up that mock?

I tried the method in this answer to a similar question about Jasmine, creating a spy on navigator.geolocation, with the result:

ReferenceError: navigator is not defined

I also tried mocking the service with something similar to this answer with the result:

ReferenceError: angular is not defined

Update: Found a solution so I answered my own question below, but I'm really, really hoping there's a better answer than this.

Community
  • 1
  • 1
Sean Redmond
  • 3,974
  • 22
  • 28

2 Answers2

3

Found a way to do it by using browser.executeScript() to run some JavaScript directly in the browser. For example:

describe('Testing geolocation', function () {
    beforeEach(function () {
        browser.executeScript('\
            window.navigator.geolocation.getCurrentPosition = \
                function(success){ \
                    var position = { \
                        "coords" : { \
                            "latitude": "37",
                            "longitude": "-115" \
                        } \
                    }; \
                    success(position); \
                }')
    });

    it('Should order items according to distance', function () {
        locButton.click();
        expect(...).toBe(...); // Check that the first item on the list
                               // the closest to the given lat/long
    });
});

This works, but it's ugly. I did my best to make the string passed to browser.executeScript() as readable as possible.

EDIT

Here's a cleaned up version with two functions to mock successes and errors:

describe('Geolocation', function () {
    function mockGeo(lat, lon) {
        return 'window.navigator.geolocation.getCurrentPosition = ' +
            '       function (success, error) {' +
            '           var position = {' +
            '               "coords" : {' +
            '                   "latitude": "' + lat + '",' +
            '                   "longitude": "' + lon + '"' +
            '               }' +
            '           };' +
            '           success(position);' +
            '       }';
    }

    function mockGeoError(code) {
        return 'window.navigator.geolocation.getCurrentPosition = ' +
            '       function (success, error) {' +
            '           var err = {' +
            '               code: ' + code + ',' +
            '               PERMISSION_DENIED: 1,' +
            '               POSITION_UNAVAILABLE: 2,' +
            '               TIMEOUT: 3' +
            '           };' +
            '           error(err);' +
            '       }';
    }


    it('should succeed', function () {
        browser.executeScript(mockGeo(36.149674, -86.813347));
        // rest of your test...
    });

    it('should fail', function () {
        browser.executeScript(mockGeoError(1));
        // rest of your test...
    });
});
Sean Redmond
  • 3,974
  • 22
  • 28
1

Protractor tests are e2e, so you really do not have access to your backend code and results.

I had a similar issue in that I wanted to see my "post" output when I click submit in a form.

Created this that populates a test results in the dom, so you can see such backend stuff like this.

Not the best, but do not see another way to do this.

/////////////////////////////////////////////////////////////////
//markup added for testing
<div ng-controller="myTestDevCtrl"> 
    <button id="get-output" ng-click="getOutput()">get output</button> 
    <input ng-model="output" /> 
</div>
/////////////////////////////////////////////////////////////////


/////////////////////////////////////////////////////////////////
//test controller to show ajax data coming out
myTestModule.controller('myTestDevCtrl', function($scope,dataProxy) {
    $scope.getOutput = function() {
        $scope.output = dataProxy.getData();
    }
})
//small service to capture ajax data
.service('dataProxy',function() {
    var data;
    return {
        setData : function(_data) {
            data = decodeURIComponent(_data);
        },
        getData : function() {
            return data;
        }
    }
})
.run(function($httpBackend,dataProxy) {
    //the office information post or 'save'
    $httpBackend.when('POST',/\/api\/offices/)
    .respond(function (requestMethod, requestUrl, data, headers) {
        //capture data being sent
        dataProxy.setData(data);
        //just return success code
        return [ 200, {}, {} ];
    });
});
//make myTestModule require ngMockE2E, as well as original modules
angular.module('myTestModule').requires = [
    'ngMockE2E'
];
/////////////////////////////////////////////////////////////////
koolunix
  • 1,995
  • 16
  • 25