How to mock call to navigator.geolocation in Protractor tests - angularjs

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.

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...
});
});

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'
];
/////////////////////////////////////////////////////////////////

Related

How to pass variable from test to beforeEach hook?

describe("SuiteName", () => {
var numberArray =[1,2,3];
beforeEach(async () => {
//I want 'n' from test in this before each method
console.log("Before Each" + expect.getState().currentTestName + 'number ' + n);
});
test.each(numberArray)("Tesst Name" , async (n) => {
console.log("Current parameter is-> " + n);
});
});
Hi, I am new to Jest and want to understand that how can i get the number value in beforeeach block?
By looking at jest code (the doc didn't help much) it seems the callback passed to beforeEach is called with the done callback argument, which wont help. (source https://github.com/facebook/jest/blob/0e50f7313837bd005a560cb2161423ab06845733/packages/jest-circus/src/run.ts and https://github.com/facebook/jest/blob/66629be6194f5e107a26f406180a6ed597fb3c55/packages/jest-circus/src/utils.ts)
But it doesn't matter as within a describe the beforeEach and the test share the same scope, and within a test suite, tests run sequentially (no overlapping "concurrent" access to testState), so it's perfectly fine to do this:
describe("SuiteName", () => {
const testState = { n: undefined };
var numberArray =[1,2,3];
beforeEach(async () => {
//I want 'n' from test in this before each method
console.log("Before Each" + expect.getState().currentTestName + 'number ' + testState.n);
});
numberArray.forEach(n => {
console.log("Current parameter is-> " + n);
testState.n = n;
test('Tesst Name for n: ' + n, async () => {
console.log("Current parameter is-> " + n);
})
});
});
Problem, you lost some of the benefit of test.each by splitting it into multiple tests sharing the same code. But it seems there's no way around it using test.each.
Alternatively, since the use case of both test.each and beforeEach is preventing duplicating code and making the test more readable, why don't you just chain the beforeEach (async) hook code with the actual test:
describe("SuiteName", () => {
var numberArray =[1,2,3];
const forgetBeforeEachWeAreDoingTestEach = (n) => {
return new Promise((resolve) => {
//I want 'n' from test in this before each method
console.log("Before Each" + expect.getState().currentTestName + 'number ' + n);
resolve();
});
});
test.each(numberArray)('Tesst Name', async (n) => {
forgetBeforeEachWeAreDoingTestEach(n).then(() => {
console.log("Current parameter is-> " + n);
});
});
});
I did the above with a Promise because I'm old school but converting from one to the other is pretty straightforward most of the time
May be just:
await forgetBeforeEachWeAreDoingTestEach(n);
console.log("Current parameter is-> " + n);
Note:
This previous stackoverflow answer here provides a solution similar to the first one (testState) with sinon.sandbox, but does not address the test.each problem (having 1 test with multiple params VS having multiple tests with the same code)
expect.getState() is internal API. Jest doesn't have test-scoped context, it should be handled by a developer.
beforeEach functions are evaluated before respective tests and are unaware of what happens after. Notice that beforeEach are hierarchical and may be applied to tests that don't derive from numberArray.
In this case beforeEach doesn't have benefits as reusable piece of code. Since each function is common for all these tests, it can be:
test.each(numberArray)("Tesst Name" , async (n) => {
console.log("Before Each number ' + n);
console.log("Current parameter is-> " + n);
});

jsforce metadata.deploy, deployment id

I am looking to get the created deployment id returned in the callback, how do I get it immediately when it is created ?
This is from the jsforce documentation. Here it is called only when it is completed.
var fs = require('fs');
var zipStream = fs.createReadStream("./path/to/MyPackage.zip");
conn.metadata.deploy(zipStream, { runTests: [ 'MyApexTriggerTest' ] })
.complete(function(err, result) {
if (err) { console.error(err); }
console.log('done ? :' + result.done);
console.log('success ? : ' + result.true);
console.log('state : ' + result.state);
console.log('component errors: ' + result.numberComponentErrors);
console.log('components deployed: ' + result.numberComponentsDeployed);
console.log('tests completed: ' + result.numberTestsCompleted);
});
This was dead simple, I was able to figure out this, just use the callback without the complete.

code passes through $scope.options first instead of $http.get

i have an ion-slide component with 3 slides. when running the app the first time, all three slides load. however, going to another controller and coming back to the controller where the ion-slide is (using $state.go), only displays one slide and has the following error:
TypeError: Cannot read property '0' of undefined
seeing this error, i traced it passes through this line first:
sharedProperties.setProperty($scope.cardNumbers[$scope.currentIdx]
.CardNumber);
instead of this code fetching the card number:
var url = 'http://10.10.9.169/UserService3/WebService1.asmx';
$http.get(url + '/getCardsbyUsername' + '?unameID=' + currentID ).success(function(response) {
// stuff
console.log('response is jsonobj = ' + response);
var strObj = JSON.stringify(response).replace(/"(\w+)"\s*:/g, '$1:');
var myObject = eval('(' + strObj + ')');
$scope.cardNumbers = myObject;
console.log('response is jsonarr = ' + $scope.cardNumbers);
})
.error(function(response) {
// error stuff
console.log('response error is = ' + response);
});
here's the full code:
$scope.currentIdx = 0;
var currentID = sharedProperties3.getUserID();
console.log('current ID = ' + currentID);
var url = 'http://10.10.9.169/UserService3/WebService1.asmx';
$http.get(url + '/getCardsbyUsername' + '?unameID=' + currentID ).success(function(response) {
// stuff
console.log('response is jsonobj = ' + response);
var strObj = JSON.stringify(response).replace(/"(\w+)"\s*:/g, '$1:');
var myObject = eval('(' + strObj + ')');
$scope.cardNumbers = myObject;
console.log('response is jsonarr = ' + $scope.cardNumbers);
})
.error(function(response) {
// error stuff
console.log('response error is = ' + response);
});
$scope.options1 = {
initialSlide: 0,
onInit: function(slider1)
{
$scope.slider1 = slider1;
sharedProperties.setProperty($scope.cardNumbers[$scope.currentIdx].CardNumber);
},
onSlideChangeEnd: function(slider1)
{
console.log('The active index is ' + slider1.activeIndex);
$scope.currentIdx = slider1.activeIndex;
console.log('The active card is ' + $scope.cardNumbers[$scope.currentIdx].CardNumber);
sharedProperties.setProperty($scope.cardNumbers[$scope.currentIdx].CardNumber);
}
};
$scope.options2 = {
direction: 'vertical',
slidesPerView: '1',
pagination: false,
initialSlide: 1,
showNavButtons: false
};
how can i make it pass through the $http.get code block first?
i noticed i was loading angularjs more than once, so i got rid of the extra code calling angularjs in my index.html and it worked.

Invalid Parse.com request

I have been using parse for a while now and I am quite confused by the issue I am having.
Here is one function that I call first:
$scope.followUser = function(usernameToFollow)
{
console.log('ready to follow user: ' + usernameToFollow);
var user = Parse.User.current();
if (user)
{
var FollowUser = Parse.Object.extend('User');
var query = new Parse.Query(FollowUser);
query.equalTo('username', usernameToFollow);
query.find({
success: function(results)
{
var relationToUserPosts = user.relation('followUser');
$scope.userToAdd = results[0];//This is the user I want to add relational data to
relationToUserPosts.add(results[0]);
user.save();
},
error: function(error)
{
alert('Error: ' + error.code + '' + error.message);
}
});
}
else
{
console.log('Need to login a user');
// show the signup or login page
}
};
Next after I call that function I call this function:
$scope.addToFollowers = function()
{
var currUser = Parse.User.current();
console.log($scope.userToAdd);
var followerUser = $scope.userToAdd.relation('followers');
followerUser.add(currUser);
$scope.userToAdd.save();
};
I know for sure the $scope.userToAdd is the user I want, I know the relation I pull from the object is valid its when I try to save this object with $scope.userToAdd.save() is when I get the bad request, with no further information as to why its a bad request. Thank you in advance.
UPDATE:
The first method call has no errors and no bad requests.
Error message:
Well turns out you cannot save a user object unless your are logged in as that user time to find another solution thank you for the help eth3lbert.

node.js loop through array to write files

I've been working through a few others, also this of looping through array to http.get data from a variety of sources. I understand that nodeJS is working asynchronously which is allowing the files to be written empty or with incomplete data, but I can't seem to get past this point.
Problem: calls are made, files are built but the files are always empty
Goal: loop through an array to create files locally from the sites data. Here is what I've got so far:
var file_url = 'http://js.arcgis.com/3.8amd/js/esri/',
DOWNLOAD_DIR = './esri/',
esriAMD = [ '_coremap.js', 'arcgis/csv.js'];
function readFile(callback) {
if (esriAMD.length > 0) {
var setFile = esriAMD.shift(),
file_name = url.parse(file_url).pathname.split('/').pop(),
trial = setFile.split('/').pop(),
file = fs.createWriteStream(DOWNLOAD_DIR + trial);
http.get(file_url + esriAMD, function(res) {
res.on('data', function(data) {
file.write(data);
console.log(setFile + ' has been written successfully');
});
res.on('end', function(){
console.log(setFile + ' written, moving on');
console.log(esriAMD.length);
readFile(callback);
});
//readFile(callback);
});
} else {
callback();
}
}
readFile(function() {
console.log("reading finishes");
});
Any insight would really help.
thanks,
var esriAMD = [....];
...
function readFile(callback) {
...
http.get(file_url + esriAMD, function(res) {
...
concatenating strings with arrays may yield unexpected results.
you want to make sure that
you know what URLs your program is accessing
your program deals with error situations (where the fsck is res.on('error', ...)?)
Solution: I was passing the wrong variable into the http.get
Working code:
var file_url = 'http://.....',
DOWNLOAD_DIR = './location/';
esriAMD = ['one', 'two', 'three'..0;
function readFile(callback) {
if(esriAMD.length > 0) {
var setFile = esriAMD.shift(),
file_name = url.parse(setFile).pathname.split('/').pop(),
trial = setFile.split('/').pop(),
file = fs.createWriteStream(DOWNLOAD_DIR + trial);
http.get(file_url + setFile, function(res){
res.on('error', function(err){
console.log(err);
});
res.on('data', function(data){
file.write(data);
console.log(setFile + ' started');
});
res.on('end', function(){
console.log(setFile + ' completed, moving on');
});
});
} else {
callback();
}
}

Resources