How to test angularJs http request jsonp - angularjs

So, i am trying to follow this tutorial while trying to create some unit tests along the way.
Well i am stuck at trying to test the $http request to the ergast developer api. As u can see in the tutorial the $http request is made to the service ergastAPIservice
angular.module('F1FeederApp.services', []).
factory('ergastAPIservice', function($http) {
var ergastAPI = {};
ergastAPI.getDrivers = function() {
return $http({
method: 'JSONP',
url: 'http://ergast.com/api/f1/2013/driverStandings.json?callback=JSON_CALLBACK'
});
}
return ergastAPI;
});
Which is then used in the controller:
angular.module('F1FeederApp.controllers', []).
controller('driversController', function($scope, ergastAPIservice) {
$scope.nameFilter = null;
$scope.driversList = [];
ergastAPIservice.getDrivers().success(function (response) {
$scope.driversList = response.MRData.StandingsTable.StandingsLists[0].DriverStandings;
});
});
How would i go about testing this (the service). I have started up something like this:
describe('F1FeederApp services', function() {
describe('ergastAPIservice', function(){
var scope, ctrl, $httpBackend;
beforeEach(module('F1FeederApp'));
beforeEach(inject(function(_$httpBackend_, $rootScope, $controller) {
scope = $rootScope.$new();
ctrl = $controller('driversController', {$scope: scope});
$httpBackend = _$httpBackend_;
$httpBackend.whenJSONP("
http://ergast.com/api/f1/2013/driverStandings.json?callback=JSON_CALLBACK").
respond(WHAT SHOULD GO HERE);
}));
it("should get ???", function () {
$httpBackend.flush();
expect(WHAT SHOULD GO HERE).toEqual('foo');
});
});});
Like i said stuck,getting this to work, i thought that what ever you put in the respond should be what u expect in your test, that it doesn't have anything to do with the actual mocked $http request...or am i getting this all wrong
Any tips and/or pointing me in the right direction would be very appreciated,
Cheers!

Related

Use $http in a controller constructor without obtaining it as a parameter

I am new to AngularJS and I am trying to create a controller for this karma test (practicing TDD, I should not change the test):
beforeEach(inject(function($injector, $rootScope) {
// Set up the mock http service responses
$httpBackend = $injector.get('$httpBackend');
$http = $injector.get('$http');
// backend definition common for all tests
apiCertificatesServerHandler = $httpBackend.when('GET', '/api/certificates/1')
.respond({"id":1,"name":"example.com","issuer":"Cert Corp.","expires":1469565860612});
// The $controller service is used to create instances of controllers
var $controller = $injector.get('$controller');
var $rootScope = $injector.get('$rootScope');
var $rootParams = $injector.get('$rootParams');
createController = function() {
return $controller('CertificateController', {'$rootScope' : $rootScope, '$scope': $rootScope.$new() '$routeParams': {id: 1}});
};
}));
and then:
it('should fetch certificate', function() {
$httpBackend.expectGET('/api/certificates/1');
var vm = createController();
$httpBackend.flush();
expect(vm.data).toEqual({"id":1,"name":"example.com","issuer":"Cert Corp.","expires":1469565860612});
});
The only way to obtain the info via GET request in the controller, (that I can think of), is obtaining $http in the constructor. But this is not what the test expects. Here is my solution, which is not ok according to the test:
function CertificateController($http, $routeParams) {
var vm = this;
$http({
method: 'GET',
url: '/api/certificates/' + $routeParams.id
}).then(function (certificate) { ...
Can you advice me how to write the controller in order the test to pass? If I instantiate it only with $rootScope and $scope, how to obtain the $http obj that I need? Can you advice me how can I get a reference to the $http parameter in this case?

Angular unit test factory that uses http request

I am working on an angular js app with karma/Jasmine testing framework, I need to test a factory that returns a http promise but it always return undefined
here is my factory
angular.module('GithubUsers').factory('Users',['$http','$q',function($http,$q){
return{
getAllUsers:function(){
var defered= $q.defer();
$http({
url:'https://api.github.com/users',
method:'GET'
}).then(function(users){
defered.resolve(users.data);
},function(err){
defered.reject(err);
})
return defered.promise;
}
}
}])
here is my tests
Update thanks to your answers I modified my code to the following but no I got this error
Possibly unhandled rejection: {"status":0,"config":{"method":"GET","transformRequest":[null],"transformResponse":[null],"jsonpCallbackParam":"callback","url":"https://api.github.com/users?since=1","headers":{"Accept":"application/json, text/plain, /"},"cached":false},"statusText":""} thrown
describe('Test Users Factory',function(){
var $controller,
Users,
$rootScope,
$httpBackend,
$q;
beforeEach(module('GithubUsers'));
beforeEach(inject(function(_$controller_,_Users_,_$rootScope_,_$httpBackend_,_$q_){
$controller = _$controller_;
Users = _Users_;
$rootScope= _$rootScope_;
$httpBackend=_$httpBackend_;
}))
it('should get users',function(){
var result;
$httpBackend.whenGET('https://api.github.com/users?since=1').respond(function(){
return {data:[{id:2}],status:200};
})
Users.getAllUsers().then(function(res){
result = res;
});
$httpBackend.flush();
$rootScope.$digest()
expect(result).toBeTruthy();
})
})
Thanks in advance!
I think you need to pass a function that returns a array with 3 items in it, to whenGET().respond().
Maybe, you can try something like this:
beforeEach(angular.mock.inject(function (User, $httpBackend, $http) {
...
this.withOKUsers = function() {
var i1 = new User();
i1.id = 10;
return [200, JSON.stringify([ i1]), {}];
} ...
}));
...
it('should get users',function(){
$httpBackend
.whenGET('https://api.github.com/users')
.respond(this.withOKUsers);
Users.getAllUsers().then(function(res){
result = res;
});
$httpBackend.flush();
expect(result).not.toBeNull();
...
(I prefer to arrange spec outside of it() clause for better readability)
You're missing a $httpBackend.flush(); call after your test method call. It will invoke a success/error or then part and resolve a $q's promise properly. For more tests I would move a $httpBackend.whenGET to each test case separately so I can later verify it per use case but it's just my personal opinion.
I find it a little suspicious that you mix a $controller and a factory in one test. I would suggest to split them, and in controller test just check the calls to service methods and in a facotry test itself do a $httpBackend stuff.
Below I paste your test with my corrections. It works now for me:
describe('Test Users Factory', function () {
var Users,
$rootScope,
$httpBackend,
$q;
beforeEach(module('app.utils'));
beforeEach(inject(function (_Users_, _$rootScope_, _$httpBackend_, _$q_) {
Users = _Users_;
$rootScope = _$rootScope_;
$httpBackend = _$httpBackend_;
}));
afterEach(function () {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it('should get users', function () {
var result;
$httpBackend.when('GET', "https://api.github.com/users").respond({ data: [{ id: 2 }], status: 200 });
Users.getAllUsers().then(function (res) {
result = res;
expect(result).toBeTruthy();
});
$httpBackend.flush();
$rootScope.$digest();
});
Important notices:
1)afterEach - check if no pending requests remain after your call
2) your url differ with a parameter ?since=1. But you do not give it as a parameter in your code so i do not understand why you added this parameter.
Maybe consider string concatenation with url and parameter ?

How to test saving a resource in a controller with a promise

I have a controller that saves a resource. I can't tell how to "access" the part of the code that executes after the promise resolves. What do I need to change about my test or controller in order to get it to work? Here's the code.
Controller:
'use strict';
/**
* #ngdoc function
* #name lunchHubApp.controller:AnnouncementsCtrl
* #description
* # AnnouncementsCtrl
* Controller of the lunchHubApp
*/
angular.module('lunchHubApp')
.controller('AnnouncementsCtrl', ['$scope', 'Announcement', function ($scope, Announcement) {
$scope.announcements = [];
$scope.save = function() {
// This next line is the part I'm finding hard to test.
new Announcement($scope.announcement).create().then(function(announcement) {
$scope.foo = 'bar'
});
};
}]);
Test:
'use strict';
describe('AnnouncementsCtrl', function() {
beforeEach(function() {
module('lunchHubApp', 'ng-token-auth')
});
it('sets scope.announcements to an empty array', inject(function($controller, $rootScope) {
var scope = $rootScope.$new(),
ctrl = $controller('AnnouncementsCtrl', { $scope: scope });
expect(scope.announcements).toEqual([]);
}));
describe('save', function() {
it('works', inject(function($controller, $rootScope, _$httpBackend_) {
var $httpBackend = _$httpBackend_;
var scope = $rootScope.$new(),
ctrl = $controller('AnnouncementsCtrl', { $scope: scope });
expect(scope.announcements.length).toBe(0);
var announcement = {
restaurantName: 'Bangkok Taste',
userId: 1
};
scope.announcement = announcement;
$httpBackend.expect('POST', '/api/announcements').respond(200, announcement);
scope.save();
scope.$digest();
expect(scope.foo).toEqual('bar');
}));
});
});
Update: here's the way I ended up modifying my controller test. The following passes and has been refactored from the original.
'use strict';
describe('AnnouncementsCtrl', function() {
var $httpBackend,
announcement,
scope,
ctrl;
beforeEach(function() {
module('lunchHubApp');
inject(function($injector) {
$httpBackend = $injector.get('$httpBackend');
scope = $injector.get('$rootScope').$new();
ctrl = $injector.get('$controller')('AnnouncementsCtrl', { $scope: scope });
announcement = { restaurantName: 'Bangkok Taste' };
scope.announcement = { restaurantName: 'Jason\'s Pizza' };
$httpBackend.expect('GET', '/api/announcements').respond([announcement]);
});
});
it('sets scope.announcements to an empty array', function() {
expect(scope.announcements).toEqual([]);
});
it('grabs a list of announcements', function() {
expect(scope.announcements.length).toBe(0);
$httpBackend.flush();
expect(scope.announcements.length).toBe(1);
});
describe('save', function() {
beforeEach(function() {
$httpBackend.expect('POST', '/api/announcements').respond(200, { restaurantName: 'Foo' });
scope.save();
$httpBackend.flush();
});
it('adds an announcement', function() {
expect(scope.announcements.length).toBe(2);
});
it('clears the restaurant name', function() {
expect(scope.announcement.restaurantName).toEqual('');
});
});
});
I think what you're doing is good. Since the Angular resources are factories using the $http service in a restful way, you should use the expect of the $httpBackend just as you did.
One thing that you miss however is that you need to make sure your promise is resolved. But write async tests can be tricky in some cases. To do so, you have to use the flush() method of $httpBackend to force your test to be synchronous.
After the flush, you can make your expect normally. Also you might have to move your expectPOST before your $rootScope.$new() statement.
You can go with a change like this, I don't think the $digest() is necessary:
$httpBackend.expect('POST', '/api/announcements').respond(200, announcement);
scope.save();
$httpBackend.flush();
expect(scope.foo).toEqual('bar');
The tests you've started writing seem to be testing not just AnnouncementsCtrl, but the Announcements service/factory as well. The signs of this in this case are
You're not mocking the Announcements service/factory / not stubbing any of its methods.
There is no code in the AnnouncementsCtrl regarding making http requests, and yet you're using $httpBackend.expect(... in the tests for them.
The success/failure of the tests that claim to test AnnouncementsCtrl will succeed or fail depending on code in the Announcements service/factory.
This goes against what unit tests are usually used for: testing each component in isolation. Keeping the focus of this answer on testing the success callback passed to the then method of the promise returned by create, my suggestion is to mock the Announcements service/factory, so its create method returns a promise that you can control in the test. This mock would be of the form:
var MockAnnouncement = null;
var deferred = null;
beforeEach(module(function($provide) {
MockAnnouncement = function MockAnnouncement() {
this.create = function() {
return deferred.promise;
};
};
$provide.value('Announcement', MockAnnouncement);
}));
You would then have to make sure that you create deferred object before each test:
beforeEach(inject(function($rootScope, $controller, $q) {
$scope = $rootScope.$new();
deferred = $q.defer(); // Used in MockAnnouncement
ctrl = $controller('AnnouncementsCtrl', {
$scope: $scope
});
}));
This deferred object is then resolved in the test:
it('calls create and on success sets $scope.foo="bar"', function() {
$scope.save();
deferred.resolve();
$scope.$apply();
expect($scope.foo).toBe('bar');
});
A slightly extended version of this, testing a few other behaviours of the controller as well, can be seen at http://plnkr.co/edit/v1bCfmSPmmjBoq3pfDsk

Issue with $httpbackend while testing Angular Controler with Karma-Jasmine

I put the code in a fiddle so it can be easily updated and 'worked with' if needed.
describe('PlayersListCtrl', function() { // Jasmine Test Suite
beforeEach(module('wc2014App'));
var ctrl, scope, $httpBackend;
beforeEach(inject(function($controller, $rootScope) {
scope = $rootScope.$new();
ctrl = $controller('PlayersListCtrl', {
$scope: scope
});
}));
it('should have an empty player array', function() {
expect(scope.players.length).toBe(0);
});
describe('PlayersListCtrl', function() {
var $httpBackend, $rootScope, createController;
beforeEach(inject(function($injector) {
$httpBackend = $injector.get('$httpBackend');
$httpBackend.when('GET', '../app/stubs/players.json').respond(
{userId: 'userX'},
{'A-Token': 'xxx'});
$rootScope = $injector.get('$rootScope');
var $controller = $injector.get('$controller');
createController = function() {
return $controller('PlayersListCtrl', {'$scope' : $rootScope });
};
}));
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it('should fetch authentication token', function() {
$httpBackend.expectGET('../app/stubs/players.json');
var controller = createController();
$httpBackend.flush();
});
});
});
The rest, cause its quite verbose, is in the fiddle: http://jsfiddle.net/tLte2/
Basically the first test passes, not a hard one, but the second one depends on a JSON stub and gives errors like:
PhantomJS 1.9.7 (Mac OS X) PlayersListCtrl PlayersListCtrl should fetch authentication token FAILED
Error: No pending request to flush !
Cant seem to get a grip on how this $httpBackend stiff works. Is must be possible to just fire it and set the result in the scope of the controller?
--edit
Basically got everything wired up perfectly and can do some simple tests that run just fine, however getting JSON stub data in there seems to be a pain. Workaround can be just defining the array described in the the JSON on the controller scope like: controller.players = ['one','two','three',..... etc ......]
But that doesnt feel right. That $httpBackend stuff shouldn't be that hard to fix right?

Testing AngularJs' $http.defaults.headers.common if specific header is set

So I'm new to the world of JavaScript and AngularJS and therefor my code is not as good as it should be yet, but it's improving. Nevertheless I started learning and implementing a simple login page with a REST Backend. After the Login-Form is submitted, a authentication-token is returned and set as a default http-header property like this
$http.defaults.headers.common['X-AUTH-TOKEN'] = data.authToken;
This works fine whenever I test it manually, but that's not the way to go so I'd like to implement a unit-test which checks if the X-AUTH-TOKEN header is set.
Is there a way to check that with $httpBackend? e.g I have the following test:
describe('LoginController', function () {
var scope, ctrl, $httpBackend;
// Load our app module definition before each test.
beforeEach(module('myApp'));
// The injector ignores leading and trailing underscores here (i.e. _$httpBackend_).
// This allows us to inject a service but then attach it to a variable
// with the same name as the service.
beforeEach(inject(function (_$httpBackend_, $rootScope, $controller) {
$httpBackend = _$httpBackend_;
scope = $rootScope.$new();
ctrl = $controller('LoginController', {$scope: scope}, {$http: $httpBackend}, {$location: null});
}));
it('should create an authToken and set it', function () {
$httpBackend.expectPOST('http://localhost:9000/login', '200').respond(200, '{"authToken":"52d29fd63004c92b972f6b99;65e922bc-5e33-4bdb-9d52-46fc352189fe"}');
scope.login('200');
$httpBackend.flush();
expect(scope.data.authToken).toBe('52d29fd63004c92b972f6b99;65e922bc-5e33-4bdb-9d52-46fc352189fe');
expect(scope.loginValidationOverallError).toBe(false);
expect(scope.status).toBe(200);
});
My Controller looks like this:
.controller('LoginController', ['$scope', '$http', '$location',
function ($scope, $http, $location) {
// Login Stuff
$scope.data = {};
$scope.status = {};
$scope.loginValidationOverallError = false;
$scope.login = function (user) {
$http.post('http://localhost:9000/login', user).success(function (data, status) {
$scope.data = data;
$scope.status = status;
$scope.loginValidationOverallError = false;
console.log($scope.status, $scope.data);
$http.defaults.headers.common['X-AUTH-TOKEN'] = data.authToken;
$location.path('/user');
}).error(function (data, status) {
console.log(status + ' error');
$scope.loginValidationOverallError = true;
});
};
...
I checked the documentation at http://docs.angularjs.org/api/ngMock.$httpBackend but am not sure if the last test is actually applicable to my code (and how that code actually tests something)
it('should send auth header', function() {
var controller = createController();
$httpBackend.flush();
$httpBackend.expectPOST('/add-msg.py', undefined, function(headers) {
// check if the header was send, if it wasn't the expectation won't
// match the request and the test will fail
return headers['Authorization'] == 'xxx';
}).respond(201, '');
$rootScope.saveMessage('whatever');
$httpBackend.flush();
});
I was facing the same issue and I finally solved it. It was very tricky
Souce code for AuthenticationService.login() function
$http.post(...)
.success(function(data) {
...
$http.defaults.headers.common['Authorization'] = data.oauth_token;
});
Test code
beforeEach(inject(function(_$httpBackend_,AuthenticationService) {
$httpBackend = _$httpBackend_;
$authenticationService = AuthenticationService;
}));
it('should login successfully with correct parameter', inject(function($http) {
// Given
...
...
var fakeResponse = {
access_token: 'myToken'
}
$httpBackend.expectPOST('oauth/token',urlEncodedParams, function(headers) {
return headers['Content-Type'] === 'application/x-www-form-urlencoded';
}).respond(200, fakeResponse);
// When
$authenticationService.login(username,password);
// Then
$httpBackend.flush();
expect($http.defaults.headers.common['Authorization']).toBe('myToken');
The trick here is that the default header is set on the real $http service, not the mocked $httpBackend. That's why you should inject the real $http service
I've tried testing the $httpBackend but got an "undefined" error because $httpBackend does not have 'defaults' property

Resources