I guess I miss something. Have spent some time trying to understand why my test is not working.
The code.
angular.module('services')
.factory('UserPreferencesService', ['$resource', function ($resource)
{
return $resource('/rest/user-preferences/:id', {},
{ getById: { method: "GET", params: { id:'#id'}} });
}
]);
The test:
it('should get by Id', function() {
//given
var preferences = {language: "en"};
httpBackend.whenGET('/rest/user-preferences/1').respond(preferences);
//when
service.getById( {id:1} ).$promise.then(function(result) {
console.log("successssssssssssssssssssssssssssssssssssss");
//then
expect(result).toEqual(preferences);
}, function() {
console.log("something wrong");
})
});
It never triggers: "successssssssssssssssssssssssssssssssssssss".
What did I miss?
There were some things wrong and other things missing in your code.
The main problem was that you were not calling the flush() function that emulates the response from the server, so the $promise was never resolved. Also, bear in mind that when the promise gets resolved, the response that you get it's a promise, meaning that this: expect(result).toEqual(preferences); won't work, but this: expect(result.lang).toEqual(preferences.lang); will.
Here you have a fixed version of your code:
The service:
angular.module('services',['ngResource'])
.factory('UserPreferencesService', ['$resource', function ($resource)
{
return $resource('/rest/user-preferences/:id', {},
{ getById: { method: "GET", params: { id:'#id'}} });
}
]);
The Test:
describe('My Suite', function () {
var httpBackend, service;
beforeEach(module('services'));
beforeEach(inject(function (_$httpBackend_, UserPreferencesService) {
httpBackend = _$httpBackend_;
service = UserPreferencesService;
}));
describe('My Test', function () {
it('should get by Id', function() {
var preferences = {language: "en"};
var result = {};
httpBackend.whenGET('/rest/user-preferences/1').respond(preferences);
service.getById({id:1}).$promise.then(function(result_) {
result = result_;
});
expect(result.language).not.toEqual(preferences.language);
httpBackend.flush();
expect(result.language).toEqual(preferences.language);
});
});
});
Working Example
I would like to add to this that I had the same issue, but was using the callback, as follows:
httpBackend.whenGET('/rest/user-preferences/1').respond(function(method, url, data) {
//do something with method, url, data...
return 200;
});
It never got hit until i replaced return 200, by return [200]. Apparently it needs an array to return.
I feel this info could be made more explicit in the documentation.
Related
I have written a factory which uses the $http get method to retrieve the json data. Now I'm trying to add a unit test for the factory to check whether the function is called or not
This is my factory
.factory('dataFactory',function($http){
function getData(){
var request = $http({
method: 'GET',
url: 'http://localhost/data.json'
});
return request;
}
var service = {
getData : getData
};
return service;
});
This is my controller
.controller('jsonController',function($scope,$state,$stateParams,stringService,dataFactory){
dataFactory.getData()
.then(function(response){
if(response){
$scope.result = response.data.record;
console.log(response.data.record);
} else {
// empty data message
}
})
.catch(function(error){
console.log('something went wrong', error);
});
});
This is my Test
describe('Testing factory', function() {
beforeEach(module('factories'));
var mySpy;
mySpy = jasmine.createSpy('service');
beforeEach(module(function ($provide){
$provide.value('service',mySpy)
}));
describe('get json List', function () {
it('should return a list', inject(function () {
spyOn(service, 'getData');
expect(service.getData).toHaveBeenCalled();
}));
});
});
its returning an error:
service is not defined
Answer
Because you're testing an API / GET return, you DON'T want a Spy! It looks like you're unit test is technically what I would call an "API" or "Integration" Test -- you're checking the data return from an endpoint. (There's nothing wrong with that, but that makes the test setup different).
AngularJS has in its ngMocks a built in service for this called $httpBacked.
Code (spec file)
describe('Testing Darshuu', function() {
beforeEach(module('Darshuu'));
// var mySpy;
var service = null;
var $httpBackend;
// Setup Spies in beforeEach() blocks!
// mySpy = jasmine.createSpy('service');
beforeEach(module(function($provide){
// $provide.value('service',mySpy)
// $provide => manually provide some object or
// function IN PLACE OF our factory
// Since we're in a Unit Test for the factory,
// we actually want to inject it and NOT mock it
// completely.
// See next beforeEach()
}));
// _..._ notation for injecting in specs
beforeEach(inject(function(_dataFactory_, _$httpBackend_) {
service = _dataFactory_;
$httpBackend = _$httpBackend_;
}));
/*
No call is ever made to getData() so this spec
*should* fail, technically.
*/
// describe('get json List', function () {
// it('should return a list', inject(function () {
// /*
// No call is ever made to getData() so this spec
// *should* fail, technically.
// */
// spyOn(service, 'getData');
// expect(service.getData).toHaveBeenCalled();
// }));
// });
describe("DataFactory", function() {
beforeEach(function() {
// Since you know to expect an $http request, use the testing
// mock for it: $httpBackend
// THEN, mock the response for the "API".
// =====
$httpBackend.whenGET('http://localhost/data.json').respond(200, {
"test": true,
"data": [{id:1, name: "Picard"}, {id: 2, name: "Riker"}, {id: 3, name: "Data"}]
});
})
it('HAS a getData() method', function() {
expect( service.getData ).toBeDefined();
expect( typeof service.getData ).toEqual("function");
})
// BECAUSE there's no AJAX mocking here OR data, it'll fail.
// I have no idea what your hosting/API is like, but this is
// a start I hope!
it("getData() - returns a JSON list (array)", function() {
service.getData().then(function(response) {
expect( response.data ).toBeDefined();
expect( typeof response.data ).toEqual("object");
});
$httpBackend.flush();
})
})
});
Plnkr
Karma+Jasmine Angular Plnkr
Define factory as object helps you to return methods in your controller as object function, for example:
app.factory('dataFactory',function($http){
var factory = {};
factory.getData = function(){
var request = $http({
method: 'GET',
url: 'http://localhost/data.json'
});
return factory;
});
Controller
app.controller("ctrl", function(dataFactory) {
dataFactory.getData(); //worked
})
I will just start out saying that $httpBackend and spyOn are pretty new to me, and I am struggling to get a clear picture of what is happening. Basically, I'm trying to do an $httpBackend.expectGET('/myroute/').respond(...) and then use spyOn, but I keep getting
Expected [ ] to equal Object({ success: true, message: 'Yeah!' })
Here's the code I'm trying to test:
angular.module('TestModule')
.service('TestService',TestService);
TestService.$inject = ['$http'];
function TestService($http){
var service = {};
service.DoIt = DoIt;
return service;
function DoIt() {
return $http.get('/myroute/')
.then(handleSuccess,handleError('Error happened!'));
}
function handleSuccess(res) {
return res.data;
}
function handleError(error) {
return function(){
return {success: false, message: error}
};
}
}
Here's my Karma-Jasmine test:
describe('Test Module',function(){
beforeEach(module('TestModule'));
describe('TestService Tests',function(){
var service,$httpBackend;
beforeEach(inject([
'$injector',
function($injector){
$httpBackend = $injector.get('$httpBackend');
service = $injector.get('TestService');
}
]));
afterEach(function(){
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it('should DoIt',function(){
var mockResponse = {
data : {success:true, message:"Yeah!"}
};
$httpBackend.expectGET('/myroute')
.respond(Promise.resolve(mockResponse));
spyOn(service,'DoIt').and.callThrough();
service.DoIt().then(function(data){
expect(service.DoIt).toHaveBeenCalled();
expect(data).toEqual(mockResponse.data); <-- ERROR OCCURS HERE -->
});
$httpBackend.flush();
});
});
});
Further information:
Using the example I found here: http://www.bradoncode.com/blog/2015/06/26/unit-testing-http-ngmock-fundamentals/, I tried this in my test code:
it('should DoIt',inject(function($http){
var promise = $http.get('/myroute').then(handleSuccess,handleError('bad stuff happened'));
promise.then(function(data){
console.log('Data: ' + JSON.stringify(data,null,3));
});
$httpBackend.expect('GET','/myroute').respond(200,{data:'Yeah!'});
$httpBackend.flush();
}));
function handleSuccess(response) {return response.data;}
function handleError(error){
return function(){
return {success:false,message:error};
};
}
This gives me the response that I expect: 'Data: {"data": "yeah"}'
My updated question: I thought service.DoIt() was doing the same thing as the promise. What is going on here? Am I not injecting my service? I do not understand how Karma expects me to stage this test. Any help, please!
If I understand your intention with the test correctly, I believe this is what you need:
describe('Test Module', function() {
var $httpBackend, service;
beforeEach(module('TestService'));
beforeEach(inject(function(_$httpBackend_, _TestService_) {
$httpBackend = _$httpBackend_;
service = _TestService_;
}));
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it('should DoIt', function() {
var mockResponse = {
data: { success: true, message: "Yeah!" }
};
$httpBackend.expectGET('/myroute/')
.respond(mockResponse);
service.DoIt().then(function(data) {
expect(data).toEqual(mockResponse);
});
$httpBackend.flush();
});
});
In my case there was transformResponse function on $resource that modified the expected mock data:
var resource = $resource('api/data/:id', {}, {
get: {
method: 'GET',
isArray: true,
transformResponse: function (data) {
var d = angular.fromJson(data);
return d.members;
}
}
...
My mock data didn't contain memebers...
I'm having an issue when I run a jasmine unit test with $httpBackend backend mock. I'm wanting to test the value returned from a service call. It's calling the service and hitting the end point but I keep getting an error from the method itself. Not sure what I'm missing in order for this to work. Any help is really appreciated.
//error
TypeError: 'undefined' is not an object (evaluating 'data[0].data')
///unit test
describe("DataService", function () {
describe('testing service', function() {
var TheService, scope;
beforeEach(function () {
module(moduleName);
module('services');
});
beforeEach(inject(function($injector,$rootScope) {
TheService = $injector.get('TheService');
scope=$rootScope.$new();
}));
it('should have a method defined', function() {
expect(TheService.getStuff).toBeDefined();
});
it('should have a method defined', inject(function($httpBackend) {
var mock = { data: 'World' };
$httpBackend.whenGET("/theurl/getStuff").respond({ data: 'World' });
$httpBackend.expectGET("/theurl/getStuff");
TheService.getStuff();
scope.$digest();
expect(TheService.getStuff).toEqual(mock);
$httpBackend.flush();
}));
});
});
//service method
function TheService($http, $q) {
var vm = this;
vm.getStuffs = null;
vm.getStuff = getStuff;
vm.theresponse = {};
function getStuff() {
var deferred = $q.defer();
$http({
method: 'GET',
url: "/theurl/getStuff"
}).success(function(data){
vm.theresponse = data[0].data
deferred.resolve(vm.theresponse);
}).error(function(){
deferred.reject('An error occurred');
});
return deferred.promise;
}
}
Well, the fake endpoint returns
{ data: 'World' }
But the service tries to access
data[0].data
data is an object. It's not an array. So data[0] is undefined.
For the service code to work fine, you should return something like
[{ data: 'World' }]
Also, your code uses a promise antipattern, and deprecated success and error callbacks. Read http://blog.ninja-squad.com/2015/05/28/angularjs-promises/ for a better alternative:
I have following controller
1) introCtrl
2) ArticleCtrl
3) articleService (Service)
Now I am sending an http request from introCrtl
.controller('IntroCtrl', function($scope, articleService) {
articleService.getArticles();
});
and AricleCtrl is
.controller('ArticleCtrl', function($scope,$rootScope,articleService) {
$scope.articles = articleService.fetchArticles();
})
and my Service is
.service('articleService', function ($http, $q) {
var articleList = [];
var getArticles = function() {
$http({
url: "muylink,co,",
data: { starLimit: 0, endLimit: 150,created_date: 0 },
method: 'POST',
withCredentials: true,
}).success(function (data, status, headers, config) {
articleList.push(data);
}).error(function (err) {
console.log(err);
})
};
var fetchArticles = function() {
return articleList[0];
}
return {
getArticles: getArticles,
fetchArticles: fetchArticles
};
});
Which is also working fine. Now Problem is that
Sometimes my http request sending respone late and i got nothing in
$scope.articles.
Can we implement watch here. How i need to implement $watch here. I dont want to implement promise. because i want to run http request behind the scene.
Thanks
It would be better if you switch to a state based setup with ui-router that way you can do this :
$stateProvider.state('myState', {
url: 'the/url/you/want',
resolve:{
articleService: 'articleService' // you are dependency injecting it here,
articles: function (articleService) {
return articleService.getArticles.$promise;
}
},
controller: 'IntroCtrl'
})
// then your controller can just inject the articles and they will be resolved before your controller loads so you it will always be fetched prior
.controller('IntroCtrl', function($scope, articles) {
$scope.articles = articles;
});
for more information take a look at this
ui-router info
All to do is set watch on articleList and provide maintaining function.
As you are watching array, it's good to change it to string.
Create function in watch which results array.
$scope.$watch( function() {
return JSON.stringify($scope.articleList);
}, function(newVal,oldVal){
//provide logic here
});
If your service result is asynchron (like http requests) you should return promises from your service.
.controller('ArticleCtrl', function($scope,$rootScope,articleService) {
articleService.fetchArticles().then(function(articles) {
$scope.articles = articles;
});
})
Service
// not sure about your service logic... simplified:
.service('articleService', function ($http, $q) {
var articleListPromise ;
var getArticles = function() {
articleListPromise = $http(/* ...*/);
};
var fetchArticles = function() {
return articleListPromise.then(function(data) {
return data[0];
});
}
return {
getArticles: getArticles,
fetchArticles: fetchArticles
};
});
I'm attempting to implement some http.get() requests in an angular service, returning a promise.
Here is the excerpt from my initial service:
angular.module('dashboard').service('DashboardHTTP', ['$q', '$http', function ($q, $http) {
this.get_info = function () {
var deferred = $q.defer();
$http.get('/dashboard/4/api/info', { cache: true }).success(function (data) {
deferred.resolve(data);
}).error(function () {
deferred.reject('Could Not Complete Request');
});
return deferred.promise;
}
}]);
And here is an excerpt from the portion of my controller where I call the service:
DashboardHTTP.get_info().then(
function (response) {
var resp = response;
$rootScope.dash_info = resp;
},
function (response) {
return 'error';
},
function (response) {
return 'notify';
});
My questions:
I'm struggling with determining how much testing is needed for an interaction like this. I currently have the following test, which is testing at the service level, but I'm wondering if I need to test at the controller level and if so what sort of testing needs to occur?
beforeEach(inject(function (_$httpBackend_, $injector) {
service = $injector.get('DashboardHTTP');
$httpBackend = _$httpBackend_;
}));
afterEach(function () {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
describe('get_info', function () {
it(' should get info from the url /api/info', function () {
var returnData = { data: 'lots of data' };
$httpBackend.expectGET('/dashboard/4/api/info').respond(returnData);
var returnedPromise = service.get_info();
var result;
returnedPromise.then(function (response) {
result = response;
});
$httpBackend.flush();
expect(result).toEqual(returnData);
});
});
My goal is that I want to set $rootScope.dash_info to the response from the HTTP request made by Service.get_info(). Is my implementation in my controller appropriate? If so, how do I test that the correct data is being passed in at the controller level?
This is probably a partial answer, but here's my input:
Your call is asynchronous, therefore your test should be. Use done.
it(' should get info from the url /api/info', function (done) {
var returnData = { data: 'lots of data' };
$httpBackend.expectGET('/dashboard/4/api/info').respond(returnData);
var returnedPromise = service.get_info();
var result;
returnedPromise.then(function (response) {
result = response;
expect(result).toEqual(returnData);
done();
});
$httpBackend.flush();
});
Also, you do know that http.get returns a promise, right? It has also success and error functions, but it is still a promise.