Mocked $location.path is returning server - angularjs

I'm testing this particular function:
function apiInjector($location, $cookies) {
var apiVersion = 1, baseUrl;
console.log('Host: '+$location.host());
if($location.host() == 'localhost') {
baseUrl = $cookies.get('API_HOST') || 'http://localhost:5000';
} else {
baseUrl = 'productionURL';
}
if(!baseUrl)
throw('Invalid host defined');
function isApiRequest(url) {
return url.substr(url.length - 5) !== '.html';
}
return {
request: function(config) {
if(isApiRequest(config.url)) {
config.url = baseUrl + '/' + apiVersion + '/' + config.url;
}
return config;
}
};
}
As you can see it makes use of $location.host to determine what the host is. I've created a mock to use so I can control the flow when it comes to the if-else statement:
var apiInjector, $location;
var mockedLocation = {
host: function() {
return 'localhost';
}
};
beforeEach(module('flowlens'), function($provide) {
$provide.value('$location', mockedLocation);
});
beforeEach(inject(function(_apiInjector_, _$location_) {
apiInjector = _apiInjector_;
$location = _$location_;
}));
describe('apiInjector',function(){
it('should be defined', function() {
expect(apiInjector).toBeDefined();
});
it('should expose a request function', function() {
expect(apiInjector.request).toBeDefined();
});
});
But when I call the function (apiInjector.request) I always see server printed when i insert a console.log ($location.host()) in the actual code (see above). What am I doing wrong here?
EDIT
This is what is printed when i print the result of apiInjector.request.
Object{url: 'productionURL/1/dashboard'}
But based on the code above and assuming that the host() function returns localhost (which it doesn't it returns server) it should print either the result of the $cookies.get or the http://localhost:5000

You need to load your module within the function passed to the beforeEach block then pass the function using $provide as a callback to module:
beforeEach(function() {
module('flowlens', function($provide) {
$provide.value('$location', mockedLocation);
});
});
Or you could write it like this:
beforeEach(module('flowlens'));
beforeEach(module(function($provide) {
$provide.value('$location', mockedLocation);
}));
Alternatively, sinon has some good options for spies, stubs, mocks, etc. I've used it to mock $location like:
var location,
locationStub;
beforeEach(inject(function($location) {
location = $location;
locationStub = sinon.stub(location, 'url');
locationStub.returns('some/fake/url');
}

Related

How to unit test condition in promise then() karma and jasmine

I am using AngularJS 1.7 with Karma and Jasmine. And I have started learning Unit Test cases.
I have a sample method below in my controller
_this.method = function () {
Service.getData().then(function (response) {
if (response.productId === "ClientAPI") {
// Some code
}
else {
// Some Code
}
}, function (error) {
_this.inProgress = false;
if (error.status === 400) {
// Some Code
} else {
// Some Code
}
})
}
Below is my test case :
describe('Some Route :: Controller => ', function () {
var $componentController;
var Service;
beforeEach(module('app'));
beforeEach(inject(function (_$componentController_, _Service_) {
Service = _Service_;
spyOn(Service, 'getData').and.callFake(function() {
var deferred = $q.defer();
var response = {};
response.productId = "ClientAPI";
deferred.resolve(result);
return deferred.promise;
});
ctrl = $componentController('controllerName', { Service: Service });
}));
it('Ctrl Method : should true', function () {
ctrl.method();
expect(Service.getData).toHaveBeenCalled();
Service.getData().then(function (response) {
expect(response.productId).toBe("ClientAPI")
})
});
});
But my branch coverage is not showing for this condition if (response.productId === "ClientAPI") {
Not sure what I am doing wrong while testing in a promise.
You need to call $scope.$apply() to trigger the call of the promise callbacks:
beforeEach(inject(function (_$componentController_, _Service_) {
Service = _Service_;
spyOn(Service, 'getData').and.returnValue($q.resolve({ productId: 'ClientAPI' }));
ctrl = $componentController('controllerName', { Service: Service });
}));
it('Ctrl Method : should true', inject(function($rootScope) {
ctrl.method();
expect(Service.getData).toHaveBeenCalled();
$rootScope.$apply();
// now test that the ctrl state has been changed as expected.
// testing that the service has returned ClientAPI is completely useless:
// the service is a mock, and you have told the mock to return that
// this should test the component, based on what you've told the service
// to return. It's not supposed to test the mock service.
// testing what the service returns tests jasmine, not your code.
});

Unit Testing angular $httpBackend service

I've got right now a project were we need to have the backend server mocked for the time being and we are using $httpBackend on the application .run feature. I need to unit test this service that contains the $httpBackend as we will be having a vast amount of mocked calls to the server we will be covering. So right now this is what I have. As a preface to my question the current setup works when I call mockDataService.getWorkflowTask from a controller on a simple page.
My Server replacement service:
angular.module('app').run(function ($httpBackend, $resource, FakeBackendService) {
// TODO: add all necessary http intercepts.
$httpBackend.whenGET('JSON file').respond(function (method, url, data) {
var request = new XMLHttpRequest();
request.open('GET', url, false);
request.send(null);
return [request.status, request.response, {}];
});
$httpBackend.whenGET(/.*/).respond(function (method, url, data) {
return [200, FakeBackendService.getWorkflowTasks(), {}];
});
});
Here is the service for FakeBackendService:
(function () {
'use strict';
var injectParams = [];
function service(lodash) {
var vm = this;
var ret = {
getWorkflowTasks: getWorkflowTasks
};
function getWorkflowTasks() {
if (vm.workflowtasks.length < 1) {
vm.workflowtasks = loadWorkflowTasks("Some JSON file");
}
return vm.workflowtasks;
};
function loadWorkflowTasks(file) {
var workflowTasks = [];
var request = new XMLHttpRequest();
request.open("GET", file, false);
request.send(null);
if (request.status == 200) {
workflowTasks = angular.fromJson(request.response);
}
return workflowTasks;
};
function init() {
vm.workflowtasks = [];
}
init();
return ret;
}
service.$inject = injectParams;
angular.module('mock.FakeBackendService', []).service('FakeBackendService', service);
})();
So that is currently the backend server replacement mock. The following is my data handling service which contains the call to $http.get(blah blah blah).
(function () {
'use strict';
var injectParams = ['$http', '$q', 'mockConfigService', '$httpBackend'];
function factory($http, $q, configService, $httpBackend) {
var vm = this;
var factory = {
getWorkflowTask: getWorkflowTask
};
function getWorkflowTask(str) {
return getResource(str);
}
function init() {
// Get the URL we will be using to get data from
vm.dataServiceURL = configService.getDataServiceURL();
}
function getResource(baseResource) {
var resource = vm.dataServiceURL + baseResource;
return $http.get(resource).then(function (response) {
if (typeof response.data == 'object') {
// Got valid response
return $q.resolve(response.data);
}
else {
// Invalid response
return $q.reject(response.data);
}
}, function (response) {
// Something went wrong
return $q.reject(response.data);
});
}
init();
return factory;
};
factory.$inject = injectParams;
angular.module('mock.dataService', []).factory('mockDataService', factory);
}());
Now for the Jasmine-Karma Unit test.
describe("HTTP Backend Mock testing", function () {
beforeEach(angular.mock.module("app"));
beforeEach(angular.mock.module("mock.FakeBackendService"));
beforeEach(angular.mock.module("mock.configService"));
beforeEach(angular.mock.module("mock.dataService"));
it("Get the workflow task", angular.mock.inject(function (mockDataService) {
var valid = "";
var promise = mockDataService.getWorkflowTask('http://localhost/foo');
promise.then(function (response) {
valid = "Success";
}, function (response) {
valid = "Failure";
});
expect(valid).toBe("Success");
}));
});
Now to the question. So, I'll start by saying I'm new to the AngularJS world and even more so to Jasmine. Anyways, when I debug the unit test I find that the promise's status is still 0 and I always get expected '' to be 'Success' telling my I never resolve (hopefully I'm using the right lingo) the promise from the $http service in mockDataService. I've tried playing around with it some and tried to see if anyone has done this kind of a thing before. I found plenty of examples where the $httpBackend is mocked in the test but none like what I'm attempting. Any ideas or suggestions would be great. Thanks.
EDIT got a slightly working solution
So I decided that I'd by pass the run() service and just do the same response in the expectGET().respond().
describe("HTTP Backend Mock testing", function () {
beforeEach(angular.mock.module("app"));
beforeEach(angular.mock.module("mock.FakeBackendService"));
beforeEach(angular.mock.module("mock.configService"));
beforeEach(angular.mock.module("mock.dataService"));
it("Get the workflow task", angular.mock.inject(function (mockDataService, $httpBackend, FakeBackendService) {
var valid = "";
$httpBackend.expectGET('http://server:80/api/foo').respond(200, FakeBackendService.getWorkflowTasks());
var promise = mockDataService.getWorkflowTask('foo');
promise.then(function (response) {
valid = "Success";
}, function (response) {
valid = "Failure";
});
$httpBackend.flush();
expect(valid).toBe("Success");
}));
});
This sort of solves my testing problem with the run() as the goal was to verify 1) That the regex matching call the correct FakeBackendService and 2) That FakeBackendService returns correct file and actually loads it. I think I can do that by mimicking the same regex in the expectGET. However, I'll leave open for a bit to see if anyone knows how to get the run() to work.
The promise is not going to resolve unless you force it to do so before the test ends. Here is one such way to do it:
$httpBackend.expectGET(......).respond(200, 'abc');
var promise = mockDataService.getWorkflowTask('http://localhost/foo');
promise.then(function (response) {
valid = "Success";
}, function (response) {
valid = "Failure";
});
//new code here
$httpBackend.flush();
expect(valid).toBe("Success");
This will force the promise to resolve and your test should pass. You'll also need to inject the $httpBackend service into the test.
angular.module('mock.dataService', [])
.service('mockDataService', function($http) {
this.getWorkflowTask = function(url) {
return $http.get(url)
}
})
describe('HTTP Backend Mock testing', function() {
var $httpBackend
beforeEach(angular.mock.module("mock.dataService"));
beforeEach(inject(function(_$httpBackend_) {
$httpBackend = _$httpBackend_
}))
it("Get the workflow task", angular.mock.inject(function(mockDataService) {
$httpBackend.expectGET('http://localhost/foo').respond(200);
var promise = mockDataService.getWorkflowTask('http://localhost/foo');
promise.then(function(response) {
valid = "Success";
}, function(response) {
valid = "Failure";
});
$httpBackend.flush();
expect(valid).toBe("Success");
}));
})
<link href="//safjanowski.github.io/jasmine-jsfiddle-pack/pack/jasmine.css" rel="stylesheet" />
<script src="//safjanowski.github.io/jasmine-jsfiddle-pack/pack/jasmine-2.0.3-concated.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular-mocks.js"></script>

Integration Testing AngularJS + Karma + Jasmine

I would like to test my angular service I would like to test it with real data - a.k.a (Integration Test). I'm using Jasmine and Karma.
Here is my test:
describe('Trending Data Service', function () {
var value = 0, originalTimeout = 0;
var service, Enums, $httpBackend;
// initialize module
beforeEach(module('waterfall'));
// initialize services
beforeEach(inject(function ($injector) {
jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;
$httpBackend = $injector.get('$httpBackend');
service = $injector.get("trendingService");
Enums = $injector.get("Enums");
spyOn(service, 'fetch').and.callThrough();
}));
it('check if dependencies are defined', function () {
expect(service).toBeDefined();
expect(Enums).toBeDefined();
expect(service.categories).toBeDefined();
expect(service.fetch).toBeDefined();
});
it('categories array should be defined within the service', function () {
expect(service.categories.length).toEqual(9);
expect(service.categories).toEqual(jasmine.any(Array));
});
// this test is alway fails...
it('fetch method should return initial result', function (done) {
var promise = service.fetch(Enums.socials.viewAll, false);
promise.then(function (result) {
done();
}, function() {
expect(1).toBe(2);
done.fail('Error occured');
});
});
}
This is the error:
Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
I tried a wide range of solutions and I haven't got any success with this.
EDIT: 29/April/2016
var trendingService = function ($q, $http) {
var deferred = $q.defer();
var $this = this;
this.fetch = function (id) {
$http.get(url).then(function (result) {
deferred.resolve(result);
}).catch(function(err) {
deferred.reject(err);
});
return deferred.promise;
}
return {
fetch: $this.fetch;
};
};
var Enums = {
Roles: {
Admin: 1,
User: 2,
NotRegistered: 0
}
};
angular.module('').const('Enums', Enums);
Karma isn't meant for integration testing. Your call to module('waterfall') is actually a reference to angular.mock.module which mocks all $https calls.
You need to use some form of end-to-end testing to test with real data. I suggest http://angular.github.io/protractor/#/.

Unknown Provider Error while trying to test generic angularjs $resource service

I have generic service that create resources for my application:
(function(module) {
module.provider('restService', {
resourceRegistry: {},
addRestResource: function(entityName, entityProto) {
this.resourceRegistry[entityName] = entityProto;
},
$get: function() {
var restService;
for (var entityName in this.resourceRegistry) {
createRestResource(entityName, this.resourceRegistry[entityName]);
};
restService = {
//createRestResource: createRestResource
};
return restService;
}});
function createRestResource(entityName, entityProto) {
console.log('registering model: ' + entityName);
module.provider(entityName, { $get: function($resource, $http) {
var resource = $resource('/api/' + entityName + '/:id', { // TODO use config
id : '#id' //this binds the ID of the model to the URL param
},{
query : { method : 'GET', isArray : true }, //this can also be called index or all
update : { method : 'PUT' },
create : { method : 'POST' },
destroy : { method : 'DELETE' }
});
// here gose some other functionality unrelated to the topic...
return resource;
}});
}}(angular.module('restService', ['ngResource'])));
I can be used by any other module using
module.config(['restServiceProvider', function(restServiceProvider) {
restServiceProvider.addRestResource('User', { name: null, email: null });
}
And while above actually works for me in the application Actually above neither works for me in the application (it was working due to some code left from before refactoring) nor I cannot get working jasmine/karma test for it. The problem is that trying various method to configure restServiceProvider I always end with error stating that for eg TestEntity there is unknown provider TestEntityProider. Although I have tried different approaches to configure the resourceRegistry before the resource is being created, here is some test file.
describe('testing restService', function () {
var httpBackend;
var theRestServiceProvider;
beforeEach(function() {
module('ngResource');
module('restService');
var fakeModule = angular.module('test.app.config', ['ngResource'], function () {});
fakeModule.config( function (restServiceProvider) {
theRestServiceProvider = restServiceProvider;
restServiceProvider.addRestResource('TestModel', {testProp: null});
});
module('test.app.config');
});
beforeEach(function () {
inject(function ($httpBackend) {
httpBackend = $httpBackend;
})
});
beforeEach(inject(function (restService) {}));
describe('create restService entity', function() {
it('should post entity with nonempty testProp',
function() {
theRestServiceProvider.addRestResource('TestModel', {testProp: null});
inject(function(TestModel) {
var object = new TestModel();
object.testProp = 'John Doe';
httpBackend.expectPOST(/.*/,
function(postData) {
console.log("post data: " + postData);
jsonData = JSON.parse(postData);
expect(jsonData.testProp).toBe(object.testProp);
return jsonData;
}).respond({});
var response = object.$create();
httpBackend.flush();
});
});
});
});
IMO this is because within the test the registering resource is being done 'too late' but still I don't know how to do this right.
EDIT here is final resolution shortcut:
(function(module) {
module.provider('restService', function($provide) {
var provider = {};
// all the provider stuff goes here
function createRestResource(entityName, entityProto) {
/*
* using $provider here is fundamental here to properly create
* 'entityName' + Provider in the runtime instead of module initialisation
* block
*/
$provide.factory(entityName, function($resource, $http) { /* ... */ };
// do other stuff...
}
return provider;
}
}(angular.module('restServiceModule', ['ngResource'])))
I've made very similar thing here https://github.com/tunguski/matsuo-ng-resource/blob/master/matsuo-ng-resource.js, maybe it will help you.
Basically I'm adding new resource providers to $provide not to module. It works. I think it's main difference.
I am not very familiar with karma/jasmine but BDD syntax seems to be similar.
In my understanding you should have something like this
beforeEach(function () {
module('restService');
inject(function (_$httpBackend_, _restService_, _ngResource_) {
$httpBackend = _$httpBackend_;
restService = _restService_;
ngResource = _ngResource_;
})
});
instead of all you beforeEach. Read more here.
If you want to provide mocks instead of injected services you would use
module('restService', function($provide){
$provide.value('serviceToMock', mockObject);
});
Note that naming module and provider/service/factory with same name may be confusing at later stage...

How to mock an angular $http call and return a promise object that behaves like $http

Is there a way to return an HttpPromise (or something similar) to mimic a call to $http? I want to set a global variable that indicates whether the real HTTP request is made or whether a fake HttpPromise object is returned with fake data.
For example, I have a service that is similar to this:
angular
.module('myservice')
.factory('MyService', ['$http', function($http) {
return {
get : function(itemId) {
if (isInTestingMode) {
// return a promise obj that returns success and fake data
}
return $http.get("/myapp/items/" + itemId);
}
};
} ]);
And in my controller, I have a call to the aforementioned service that looks similar to this:
// Somewhere in my controller
MyService.get($scope.itemId)
.success(function(data) {
$scope.item = data;
})
.error(function(data, status, headers, config) {
$scope.notFound = true;
});
I'm trying to not change the controller code; I want the success and error chaining to still work when in my "isInTestMode".
Is it possible to fake an HttpPromise in the way that I described in the service?
Below is a revised edition of the "MyService" above (a snippet) containing a success and error on the promise object. But, how do I execute the success method?
return {
get : function(itemId) {
if (isInTestingMode) {
var promise = $.defer().promise;
// Mimicking $http.get's success
promise.success = function(fn) {
promise.then(function() {
fn({ itemId : "123", name : "ItemName"}, 200, {}, {});
});
return promise;
};
// Mimicking $http.get's error
promise.error = function(fn) {
promise.then(null, function(response) {
fn("Error", 404, {}, {});
});
return promise;
};
return promise;
}
return $http.get("/myapp/items/" + itemId);
}
}
Just use the deferred method of the $qservice
var fakeHttpCall = function(isSuccessful) {
var deferred = $q.defer()
if (isSuccessful === true) {
deferred.resolve("Successfully resolved the fake $http call")
}
else {
deferred.reject("Oh no! Something went terribly wrong in your fake $http call")
}
return deferred.promise
}
And then you can call your function like an $http promise (you have to customize whatever you want to put inside of it, of course).
fakeHttpCall(true).then(
function (data) {
// success callback
console.log(data)
},
function (err) {
// error callback
console.log(err)
})
I found that this post is similar to what I was asking.
However, I wanted a way to mock my service call so that fake data could be returned instead of issuing a true HTTP request call. The best way to handle this situation, for me, is to use angular's $httpBackend service. For example, to bypass a GET request to my "items" resource BUT to not bypass GETs of my partials/templates I would do something like this:
angular
.module('myApp', ['ngMockE2E'])
.run(['$httpBackend', function($httpBackend) {
$httpBackend
.whenGET(/^partials\/.+/)
.passThrough();
$httpBackend
.whenGET(/^\/myapp\/items\/.+/)
.respond({itemId : "123", name : "ItemName"});
}]);
See this documentation for more information on $httpBackend.
I finally found a way using jasmin. $httpBackend was no option for me, as there were also non-$http-methods I needed mock on the same service. I also think that the controller test needing to specify the url is not perfect as imho the controller and its test should not need to know about it.
Here is how it works:
beforeEach(inject(function ($controller, $rootScope, $q) {
scope = $rootScope.$new();
mockSvc = {
someFn: function () {
},
someHttpFn: function () {
}
};
// use jasmin to fake $http promise response
spyOn(mockSvc, 'someHttpFn').and.callFake(function () {
return {
success: function (callback) {
callback({
// some fake response
});
},
then: function(callback) {
callback({
// some fake response, you probably would want that to be
// the same as for success
});
},
error: function(callback){
callback({
// some fake response
});
}
}
});
MyCtrl = $controller('MyCtrl', {
$scope: scope,
MyActualSvc: mockSvc
});
}));
You can implement your FakeHttp class:
var FakeHttp = function (promise) {
this.promise = promise;
this.onSuccess = function(){};
this.onError = function(){};
this.premise.then(this.onSuccess, this.onError);
};
FakeHttp.prototype.success = function (callback) {
this.onSuccess = callback;
/**You need this to avoid calling previous tasks**/
this.promise.$$state.pending = null;
this.promise.then(this.onSucess, this.onError);
return this;
};
FakeHttp.prototype.error = function (callback) {
this.onError = callback;
/**You need this to avoid calling previous tasks**/
this.promise.$$state.pending = null;
this.promise.then(this.onSuccess, this.onError);
return this;
};
Then in your code, you would return a new fakeHttp out of the promise.
if(testingMode){
return new FakeHttp(promise);
};
The promise must be asynchronous, otherwise it won't work. For that you can use $timeout.
easy peasy!
You can do it using angular-mocks-async like so:
var app = ng.module( 'mockApp', [
'ngMockE2E',
'ngMockE2EAsync'
]);
app.run( [ '$httpBackend', '$q', function( $httpBackend, $q ) {
$httpBackend.whenAsync(
'GET',
new RegExp( 'http://api.example.com/user/.+$' )
).respond( function( method, url, data, config ) {
var re = /.*\/user\/(\w+)/;
var userId = parseInt(url.replace(re, '$1'), 10);
var response = $q.defer();
setTimeout( function() {
var data = {
userId: userId
};
response.resolve( [ 200, "mock response", data ] );
}, 1000 );
return response.promise;
});
}]);

Resources