is there a way to test the data that is set on an angular service after a restful call, here is the service I am setting up. It just has two service calls one to fetch for the widgets and the other to get the widgets, If I then run the test to fetch the widgets, I expect the service to have widgets on a success.
angular.module('myApp').factory('myService', function() {
var service, widgets;
widgets = void 0;
return service = {
widgets: widgets,
fetchWidgets: function() {
var promise;
return promise = $http.get('/fetch/widgets').then(function(response) {
return widgets = response;
});
}
};
return service;
});
and the test:
describe('myService', function() {
var $httpBackend, service;
service = void 0;
$httpBackend = void 0;
beforeEach(function() {
module('myApp');
return inject((function(_this) {
return function(_$httpBackend_, myService) {
$httpBackend = _$httpBackend_;
return service = myService;
};
})(this));
});
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
return $httpBackend.verifyNoOutstandingRequest();
});
return describe("fetchWidgets", function() {
return it("should set the widgets on the service", function() {
var responseData;
responseData = [
{
id: 1
}
];
$httpBackend.whenGET('/fetch/widgets').respond(function(status, data) {
return [200, responseData];
});
expect(service.widgets).toBeUndefined();
service.fetchWidgets().then(function() {
return expect(service.widgets).toEqual(responseData);
});
return $httpBackend.flush();
});
});
});
I keep seeing service.widgets as always undefined, but i see the success block being executed. what am I missing?
There are still a few javascript gotchas left to be discovered on this journey. I needed to call a method on the service not directly access the properties. this works:
In the service add this method to access the property:
getWidgets: function() {
return widgets;
},
And in the test you can now access the values
expect(service.getWidgets()).toBeUndefined();
service.fetchWidgets()
$httpBackend.flush();
expect(service.getWidgets()).toEqual(responseData);
Related
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/#/.
I'm doing unit testing for the first time and I'm trying to work out how to mock a data call from a service so I can test if the data is coming back in the correct form:
My Service
angular.module('app.core')
.factory('PeopleService', PeopleService)
function PeopleService($http, $q, $filter) {
var endpoint;
var service = {
customers: {
value: null
},
getAllCustomers: getAllCustomers,
};
return service;
function getCustomers(endpoint_) {
endpoint = endpoint_;
service.customers.value = [];
return handleFetch($http.get(endpoint));
}
function handleFetch(promise) {
return promise.then(function (resp) {
service.customers.value = service.customers.value.concat(resp.data.data);
});
}
function getAllCustomers() {
return $q.all([
getCustomers('/api/customers'),
]).then(function(responses) {
return responses[0];
});
}
}
My Controller
angular.module('app.people')
.controller('peopleCtrl', peopleCtrl);
function peopleCtrl($scope, PeopleService) {
$scope.customers = PeopleService.customers;
getCustomers();
function getCustomers() {
return PeopleService.getAllCustomers().then(function () {
return PeopleService.customers.value;
});
}
}
My Test
describe('People Service', function () {
var controller;
var customers = mockData.getMockCustomers(); // my fake customers array
beforeEach(function() {
bard.appModule('app');
bard.inject('$controller', '$q', '$rootScope', 'PeopleService');
var ps = {
getAllCustomers: function() {
return $q.when(customers);
}
};
controller = $controller('peopleCtrl', {
$scope: $rootScope,
PeopleService: ps
});
});
it('should return an array of 5 customers', function() {
$rootScope.$apply();
expect($rootScope.customers).to.have.length(5);
});
});
I've got a controller set up that when loaded talks to the People Service and gets my customers and saves the array of customers to PeopleService.customers.value. Inside my controller, I have a variable $scope.customers which is equal to PeopleService.customers.
I'm trying to mock this with my test, without hitting the API, I'm using some mock data to do this (an array of 5 customers), but not sure if I understand correctly.
Is the idea to have my mock people service return exactly what the actual people service returns? I'm kind of confused at this point. I basically want that test to check if the mock data length is equal to five.
Any help with this is appreciated. Thanks in advance!
I'm attempted to unit test a service. I've injected the service however the method call getAllProducts() doesn't appear to run however the test still passes!
Plnkr
service.js
angular.module('vsApp')
.factory('productsDataService', function($http) {
var service = {
getAllProducts: getAllProducts
};
// get all products
function getAllProducts() {
return $http.get('/apiv1/getAllProducts/').then(function(data) {
return (data);
});
}
return service;
});
spec.js
// jasmine
describe('products data service', function () {
var $httpBackend, productsDataService;
beforeEach(module('vsApp'));
beforeEach(inject(function(_$httpBackend_, _productsDataService_) {
$httpBackend = _$httpBackend_;
productsDataService = _productsDataService_;
}));
it('should get all products', inject(function() {
console.info("get all");
// mock response for the http call in the service
$httpBackend.when('GET', '/apiv1/getAllProducts/')
.respond({name: 'item', price: '932'});
//this doesn't seem to run??
productsDataService.getAllProducts().then(function(response) {
expect(response.data.length).toBeGreaterThan(1);
});
}));
});
Ok, you have to make it sync. (all pending request will get resolved) using $http.flush();
Working demo as expected
productsDataService.getAllProducts().then(function(response) {
console.log(response);
expect(response.data.length).toBeGreaterThan(999);
});
$httpBackend.flush(); // <=============== here.
Im having problem using $inject.get in angular js..
Let say i have angular services like this
app.service("serviceOne", function() {
this.dialogAlert = function() {
return 'Message One'
};
});
app.service("serviceTwo", function() {
this.dialogAlert = function() {
return 'Message Two'
};
});
app.service("serviceThree", function() {
this.dialogAlert = function() {
return 'Message Three'
};
});
And using the factory to dynamically call dialogAlert()
app.factory("alertService", function($window, $injector) {
if ($window.servicesOne) {
return $injector.get("serviceOne");
} else {
return $injector.get(["serviceTwo", "serviceThree"]);
}
});
With this kind of codes, it gives me "unknown provider".
Or is there any alternative solution for this?
Thanks guys.
injector.get takes only one service name as argument, array is not supported, you may want to do return array of service instances by doing return ["serviceTwo", "serviceThree"].map($injector.get):-
app.factory("alertService", function($window, $injector) {
var service = ["serviceOne"];
if (!$window.servicesOne) {
service = ["serviceTwo", "serviceThree"];
}
return service.map($injector.get); //To be consistent send back this as well as array
});
So with this when you inject the alertService it will return an array of dependecy(ies).
app.controller('MainCtrl', function($scope, alertService) {
// alertService will be array of dependecies.
console.log(alertService.map(function(itm){return itm.dialogAlert()}));
});
Demo
or return with a map:-
app.factory("alertService", function($window, $injector) {
var service = ["serviceOne"],
serviceObjs = {};
if (!$window.servicesOne) {
service = ["serviceTwo", "serviceThree"];
}
angular.forEach(service, function(itm){
serviceObjs[itm] = $injector.get(itm);
});
return serviceObjs;
});
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;
});
}]);