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...
Related
I have an AngularJS service for a restful API:
angular
.module('app', [
])
.service('api', ['$http', '$q', function APIService($http, $q) {
this.get = function (dataProperty, params) {
return $http({
method: 'get',
url: 'https://some.api/rest/',
params: angular.extend({
default_params...
}, params)
})
.then(
function (result) {
if (result.data.status === 'ok') {
return result.data[dataProperty];
} else {
return $q.reject(angular.extend(new Error(result.data.message), { result: result.data }));
}
},
function (reason) {
return $q.reject(angular.extend(new Error('AJAX request to the API failed'), { reason: reason.data }));
});
};
}]);
I'm trying to test this api.get with the following:
describe('api', function () {
var
$httpBackend,
service;
beforeEach(module('app'));
beforeEach(inject(function (_$httpBackend_, _api_) {
$httpBackend = _$httpBackend_;
service = _api_;
}));
afterEach(function () {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it('', function () {
$httpBackend
.when('get', 'https://some.api/rest/')
.respond({
data: {
status: 'ok'
}
});
service.get('status', {});
$httpBackend.flush();
$httpBackend
.expect('get', 'https://some.api/rest/');
});
});
But I'm getting the error callback every time:
Error: AJAX request to the API failed in bower_components/angular-mocks/angular-mocks.js (line 279)
Am I going about setting up the test correctly? I believe the .when and .response is used to fake the actual $http call, but I can't get the success callback to fire.
The two issues were .when not looking for the correct URL (because get params were thrown in I needed to make it a regex:
.when('GET', /https:\/\/api\.flickr\.com\/services\/rest\/.*/)
Then, the .respond doesn't need to be padded with a data object, it does that for you:
.respond({ stat: 'ok' });
I would like to test $resource success and error callbacks in my controller. I don’t want to use $httpBackend as that would be used to test the data service. It seems that there is no way to do it though - the only solution I have found is to use promises instead which I could either resolve or reject. Does this sound right? Anyway, here is what I have at the moment - currently it only tests whether the $resource get() is called:
The controller:
angular
.module('myModule')
.controller('MyCtrl', MyCtrl);
MyCtrl.$inject = [
'dataService'
];
function MyCtrl(
dataService
) {
var vm = this;
vm.getData = getData;
function getData() {
dataService.getData().get(function(response) {
// stuff to test
},
function(error) {
// stuff to test
});
}
The test:
describe('Controller: MyCtrl', function() {
var MyCtrl;
var rootScope;
var scope;
var dataServiceMock = {
getData: jasmine.createSpy('getData')
};
beforeEach(function()
inject(function($controller, $rootScope) {
rootScope = $rootScope;
scope = $rootScope.$new();
MyCtrl = $controller('MyCtrl as vm', {
dataService: dataServiceMock,
});
});
});
describe('vm.getData()', function() {
beforeEach(function() {
dataServiceMock.getData.and.returnValue({
get: jasmine.createSpy('get')
});
});
it('gets the data', function() {
scope.vm.getData();
expect(dataServiceMock.getData().get).toHaveBeenCalled();
});
});
});
Try this
function getData (query) {
var deferred = $q.defer();
var httpPromise = $resource(query,{},{
post:{
method:"GET",
isArray: false,
responseType: "json"
}
});
httpPromise.post({}, {},
function(data) {
try {
var results = {}
results.totalItems = data.response;
deferred.resolve(results);
} catch (error) {
console.log(error.stack);
deferred.reject();
}
},
function(error) {
deferred.reject();
}
);
return deferred.promise;
}
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:
Here is my factory in my app.js
app.factory('userInfoFacrory', ['$http' , "$q", function($http,$q){
return {
getNames:function(){
var differed = $q.defer();
$http.get("http://localhost/ang/api/v1/users/names")
.success(function(data) {
differed.resolve(data);
}).error(function(msg) {
differed.reject(msg);
});
return differed.promise;
}
}
}])
I use this factory in my controller like bellow , and it works fine :
app.controller('mainController', ['$scope','userInfoFacrory','$log', function($scope,userInfoFacrory,$log){
var promise = userInfoFacrory.getNames();
promise.then(function (data) {
$log.info(data); // I get my data correctly here
}, function (msg) {
$log.error(data);
})
}])
And here , I've tried to write a test unit , with karma-jasmine
describe('userInfoFacrory', function() {
var factory ,$rootScope,$scope,$q,onTaskComplete , promise;
beforeEach(function() {
module("testApp");
inject(function ($injector) {
$q = $injector.get("$q");
factory = $injector.get("userInfoFacrory");
$rootScope = $injector.get("$rootScope");
$scope = $rootScope.$new();
promise = factory.getNames(); // this function comes from my factory which returns a promise
});
});
it('should return a promise', function() {
// This test will pass , so no error so far
expect(typeof promise.then).toEqual('function');
});
});
But I can't figure out how to test to so if my promise will have my data ( that comes from my api ) or not , any suggestion would be appreciated.
thanks
it('should return a promise resolved with the http response data if the http request is successful', inject(function($httpBackend) {
var expectedData = 'fake data';
$httpBackend.expectGET('http://localhost/ang/api/v1/users/names').respond(expectedData);
var promise = factory.getNames();
var actualData;
promise.then(function(result) {
actualData = result;
});
$httpBackend.flush();
expect(actualData).toEqual(expectedData);
}));
it('should return a promise rejected with the http response data if the http request is in error', inject(function($httpBackend) {
var expectedData = 'fake data';
$httpBackend.expectGET('http://localhost/ang/api/v1/users/names').respond(400, expectedData);
var promise = factory.getNames();
var actualData;
promise.catch(function(result) {
actualData = result;
});
$httpBackend.flush();
expect(actualData).toEqual(expectedData);
}));
Working plunkr: http://plnkr.co/edit/NfO6KXWLs1QT5HG8MK0J?p=preview
Note that your code is correct, but doesn't really leverage the chaining capabilities of promises. It could simply be written as
getNames: function() {
return $http.get("http://localhost/ang/api/v1/users/names")
.then(function(response) {
return response.data;
}, function(response) {
return $q.reject(response.data);
});
};
}
Working plunkr: http://plnkr.co/edit/C5x8wRYCQ0wetjozEd0a?p=preview
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.