How to test a loop inside an AngularJS service - angularjs

I'm trying to testing service but i don't know how to test loop inside. Is it possible to test case with host.lenght===1 in different way than mock url that return host.length=1. Do I have to callFake all my code inside service to test it? Also how to test isNaN?
Here's my code:
if (host.length === 1 || !isNaN(host[host.length - 1])) {
name = a.hostname;
} else {
if (host[0] === "www") {
slice = 1;
}
name = host.slice(slice, host.length - 1).reverse().join(" ");
}
jsfiddle Demo

In this case, it looks simplest to just use a URL that will give you this behaviour. In my opinion, this will also be a better test as it will actually test the intent of the code, rather than just ensuring that certain methods are called. After all, this code was written to cope with different types of URL, why not test them with exactly that?
You already have a test for this url: 'http://angular.com/about'. For the case you are talking about, you could use 'http://angular/about' and then 'http://www.angular.com/about' to test the code that strips out the www. part.

Related

Unit Test Multiple HTTP Requests

So in my factory I have a loop which requests HTTP calls and adds them to a promise array.
I then do a $q.all on the result to build a model.
When I come to test this however I can't get HTTP to make all the calls, it only makes the last one, I need it to make all the calls and build the model.
Below is very cut down code, ( I use 7 dates, but wanted to keep things short)
Factory Code
function getLatestData(){
var dateArray= ['2017-09-21','2017-09-22']
for (i = 0; i < 2; i++) {
var url = 'data-server/date/[i]'
promises.push(getData(url)); // getData is a simple $http function call.
}
return $q.all(promises).then(function(response){
buildModel(reponse);
});
}
So when I come to test this, I've got something like (I did try a loop but that failed).
httpBackend.expectGET('data-server/date/2017-09-21' ).respond(mockData[0]);
httpBackend.expectGET('data-server/date/2017-09-22' ).respond(mockData[1]);
rootScope.$apply();
modelFactory.getLatestData().then(function(response){
expect(response).toEqual(mockModelData);
})
So I console.log the get URL and I see all the URL requests are the same, they don't seem to be updating which results in this error
Error: Unexpected request: GET 'data-server/date/2017-09-22'
Expected GET 'data-server/date/2017-09-21'
because it's always the last httpBackend.expectGET that's taken.
What am I missing?
My problem was mocking.
I left this out of my example because I thought it wasn't relevant and added complication, but to build the dates I used the momentJS Library.
so
var url = 'data-server/date/[i]'
is
var url = 'data-server/date/'+factory.getMoment().add(i,'d').format('YYYY-MM-DD');
The factory.getMoment is just a wrapper for moment, the idea being I could over ride this in the unit tests to provide me with a 'given' date object.
funciton getMoment(){
return moment();
}
Anyway in my tests, I had this
var mockDate = moment('2017-09-21');
spyOn(factory, 'getMoment').and.returnValue(moment('2017-09-21'));
httpBackend.expectGET('data-server/date/'+mockDate.format('YYYY-MM-DD').respond(mockData[0]);
httpBackend.expectGET('data-server/date/'+mockDate.add(1,'d').format('YYYY-MM-DD').respond(mockData[1]);
Thinking that each time this would be called, it would give me back this mock, guess I was wrong about that.
What I needed to do, as pointed out by a colleague, was to use jasmine's clock mock.
beforeEach(function () {
jasmine.clock().install();
})
afterEach(function () {
jasmine.clock().uninstall();
})
Then in my test I set up the date time with
mockDate = moment('2017-09-21'); // always use moment as JS date if badly broken and just can't be trusted!
jasmine.clock().mockDate(mockDate.toDate());
httpBackend.expectGET('data-server/date/'+moment().add(0, 'days').format("YYYY-MM-DD").respond(mockData[0]);;
httpBackend.expectGET('data-server/date/'+moment().add(1, 'days').format("YYYY-MM-DD").respond(mockData[1]);;
(the above is in a loop)
I've removed my spy and now I have the dates and requests working as I expected.
Hope this helps someone else who finds themselves scratching their head for days trying to figure out why their tests are not working!

if url contains string, do something, else do another or add paramter to console which runs protractor

if(browser.getCurrentUrl().
toString.contain('local')){//if it is local,do something.if it is not local, i need to do another things
//do something
}
else{
//do another
}
I need to get url and check if it contains local. If it has local, i need to do operations for local. Else, i will do another operations.
I cant do this with expect because it will give failure if is was not matched.
So i need to use string operatipns.
But none of those worked:
browser.getCurrentUrl().toString.contain('local')
browser.getCurrentUrl().toString.contains('local')
browser.getCurrentUrl().contain('local')
browser.getCurrentUrl().contains('local')
error is
toString.contain is not a function
ANOTHER SOLUTION
While running protractor from console,
gulp protractor
i run with this.
SO if i can do like this:
gulp protractor local
or for another usages,
gulp protractor testmachine
it will be better. It can record that parameter to a variable so i will access that variable
if(paramter.equals="local"){//if it is local,do something.if it is not local, i need to do another things
//do something
}
else{
//do another
}
i can also do something like that instead of currenturl
if(logoutPageObject.baseUrl.toString.contain("local")){ because baseurl variable can say if i am on local or not. but also this doesnot work.
browser.getCurrentUrl() will return you a promise and not a string. First resolve the promise and use the exact URL for the validation.
browser.getCurrentUrl().then(function(url){
if(url.indexOf("local") >= 0 ){
console.log("url contains local")
}else{
console.log("do something else")
}
})

CucumberJs skipping step definition - maybe callback last parameter in step definition?

I just started working with cucumberJs, gulp and protractor for an angular app and noticed, luckily as all my steps were passing, that if you don't pass in and use that 'callback' parameter in the step definition, cucumberJs might NOT know when this step is completed and will skip other steps and mark them all as 'passed'
Below is an example from the cucumberJs doc: https://github.com/cucumber/cucumber-js
Example 1:
this.Given(/^I am on the Cucumber.js GitHub repository$/, function (callback) {
// Express the regexp above with the code you wish you had.
// `this` is set to a World instance.
// i.e. you may use this.browser to execute the step:
this.visit('https://github.com/cucumber/cucumber-js', callback);
//
The callback is passed to visit() so that when the job's finished, the
next step can
// be executed by Cucumber
.
});
Example 2:
this.When(/^I go to the README file$/, function (callback) {
// Express the regexp above with the code you wish you had.
Call callback() at the end
// of the step, or callback(null, 'pending') if the step is not yet implemented:
callback(null, 'pending');
});
Example 3:
this.Then(/^I should see "(.*)" as the page title$/, function (title, callback) {
// matching groups are passed as parameters to the step definition
var pageTitle = this.browser.text('title');
if (title === pageTitle) {
callback();
} else {
callback(new Error("Expected to be on page with title " + title));
}
});
};
I understand you have 2 choices here:
a. Either you return a promise and don't pass the call back OR
b. You pass in the callback parameter and call it whenever the step definition is complete so cucumberJs knows to return and go to next step or next scenario.
However, I tried both above and still running into a weird situation where the top two scenarios will work NORMALLY as you would expect, but the third and fourth scenario within that same feature file will be skipped and all passed.
Is there anything special to consider about features with more than 2 scenarios?
As long as I have <= 2 scenarios per feature files, everything works fine, but the moment I had a third scenario to that feature file, that third scenario is ignored and skipped.
Any ideas?
Without seeing your actual steps I can't say for sure but it sounds like an asynchronous issue or dare I say it, a syntax error in the scenario. Have you tried changing the order of the scenarios to see if that has an impact.

How do I test my HTTP call when I use a config to build the URL?

In my project I build some of my HTTP requests like so:
var options = {
params:{
foo: 'bar'
hello: world
}
};
$http.get("my/service", options)
Which means that the final HTTP call looks something like my/service?foo=bar&hello=worldVar
How do I setup my $httpBackend to account for this?
The problems I see are:
I'm not guaranteed order in my parameters with this style, which means setting up the first parameter in expectGet will be hard.
Its hard to test calls when I really don't care about the parameters
The best solution I've found to this is to use the override of expect/when that accepts a function, and to mix it in with this answer. That gets me to something like
$httpBackend.expectGET(function(url){
var parser = document.createElement('a');
parser.href=url;
return parser.search.indexOf('foo=bar') > -1 &&
parser.search.indexOf('hello=world') > -1;
})
or if I don't care about the parameters I can just test for parser.pathname.
However, this still isnt perfect because its a pain to test that I dont have extra parameters, so Im still actively looking for an alternative solution.
if you don't care about the parameters, you can just use regex to match to the static part of the URL:
$httpBackend.whenGET(/my\/service/)

how to create new DataService Layer for $http call in angularjs

If I user factory in angularjs for $http call then I would use it like below,
app.factory('myDataService', function($http){
return{
getProducts:function()
{
return $http.get('api/Product');
}
}
});
and use it in controller like this,
app.controller('appCtrl',function($scope,myDataService){
//Get Products
myDataService.getProducts().success(function(data,status){
//some stuff
}).error(function(data,status){
//some stuff
})
});
This is a simple way to make call to web api......
But
here i want is to add new layer called myDataService layer which will be helpful to define get, put ,post, delete methods in simple way like this,
Note:(Here I'm giving only concept. I dont know real implementation of it.
I dont know what should i use here .factory .service or javascript simple function. But let's say I'm using factory as below
myDataService
app.factory("myDataService", function (myHttPLayer) //myHTTPLayer is injected
{
//below code is not correct (I want to connect it to myHTTPLayer somehow)
return{
var myVar={
get:{
products:'/api/Product',
companies:'/api/Companies'
},
post:{
product:'/api/Product',
company:'/api/Companies'
}
}
}
});
here what i want is to introduce new layer like this which only gives information about get,put,post methods and api calls....
I don't want to write $http.get, $http.post and all everytime in angular controller. I want to write them at one place only one time. like this,
some.js...
myHTTPLayer
app.factory('myHttPLayer',function($http){
return {
get:function()
{ return $http.get(url);},
post:function(obj)
{ return $http.post(url,obj)
}
});
I want to connect both factories and by using them or writing them at angular controller side, i want to make web api call.
Note: This is just a concept.
please help me ....
In my mind what i want to implement is,
in angular controller something like,
app.controller("appCtrl",function($scope,myDataService){
//Connect myDataService to myHTTPLayer internally so I can use them as below....
//I want my api to be called when i write myDataService.getProducts() shown as below ....
myDataService.getProducts().success(function(){})
.error(function(){});
})
A big note : edit
Dont consider single line of code of myDataSevice. Its totally wrong. Just consider that i have all posible http methods in one factory with their proper prototyes which can return promise in myHTTPLayer. I will not temper it , once it is written correctly. What i want to do is to write myDataService code such a way that i can use it in angular controller without using myHTTP layer stuff in controller. When i use myDataService in controller, it must use httplayer behind the scene and it must make angular http call to defined web api. This is just a concept to be implemented.I want to develop such architecture. But unable to find the answer.
#stackg91 posted a great answer, but I believe it can be simplified further with regard to the else block. The below will work no matter what your 'variable' is ('products', 'company', etc.).
app.factory('myHttPLayer',function($http){
return {
get: function(variable) {
if(variable == null){
return $http.get(url);
} else {
return $http.get(url + '/api/' + variable);
}
}
}
});
Then you could simply call myHTTPLayer.get(products) in your controller for example. I don't believe you need three layers to get the functionality you're looking for here. I think a controller which passes a parameter to a service making '$http calls' utilizing that parameter is enough.
I normally do it like that, hope this helps you
app.factory('myDataService',function($http){
return {
getProducts:function(variable, callback) // this variable can be products or companies
{
if(variable == null){
return $http.get(url).success(callback).error(callback);
}else if(variable == products){
return $http.get(url+'/api/Product').success(callback).error(callback);
}else if(variable == companies){
return $http.get(url+'/api/Companies').success(callback).error(callback);
}
}
});
In COntroller it would look like this
$scope.test = myHttPLayer.get(products).onsuccess(function(){
Do Something ....
});

Resources