Angular mock $httpBackend give No pending request to flush - angularjs

Following the official guide at angularJS $httpBackend I'll do this test, but Karma give me this error:
Error: No pending request to flush !
at Function.$httpBackend.flush
Test
'use strict';
describe('profileCtrl', function () {
var scope, $httpBackend;
beforeEach(angular.mock.module('maap'));
beforeEach(angular.mock.inject(function($rootScope, $controller, _$httpBackend_){
$httpBackend = _$httpBackend_;
$httpBackend.when('GET', 'profile').respond([{id: 1, name: 'Bob'}]);
scope = $rootScope.$new();
$controller('profileCtrl', {$scope: scope});
}))
it('should fetch list of users', function(){
$httpBackend.flush();
expectGET(scope.current_user.length).toBe(1);
expect(scope.current_user[0].name).toBe('Bob');
});
});
for this simple controller:
'use strict';
angular.module('maap').controller('profileCtrl', function($scope, UserService) {
$scope.current_user = UserService.details(0);
});

The _$httpBackend_ has nothing to flush because you don't make any http request in your test.
You need to invoke some code that make an http request.
Then, once something somewhere made an http request in your test, you can call the flush method so that a response is provided for the request that has been made.
Something like:
it('should fetch list of users', function(){
// Do something that will make an http request
MyService.getAllUser(function ...) // for example
// Then provide a response for the http request that
// has been made when getAllUser was called
$httpBackend.flush();
// Check the expected result.
expect(something).toBe('Bob');
});

Same issue happened to me and the problem was not that I was not making a request but because the request I was making was different to the expected one:
For example I have defined this expectation:
mockBackend.expectGET("http://myapi.com/001").respond("RESULT");
And I was requesting this other URL:
http://myapi.com/002
Very confusing error message, no really easily related with the underneath problem.

I had the same issue, because I neglected to define a response for each expected request. With no response, there becomes nothing to flush. Also the http promise would never resolve or fail.

I've got the same exception because I used ngMockE2E module instead of ngMock module. Even calling $rootScope.$digest() didn't help.

Maybe you are not using $http AngularJs service in your ajax call.
I happened to me where I expected $httpBackend to flush something, but I got an error:
Error: No pending request to flush !
so after a long investigation I found out that the ajax was written in jQuery instead of $http
So just make sure that you use $http service for your Ajax calls.

Related

Testing the API responses format returned by angular service with Jasmine

The majority of the problems occur when the format of the interaction between me and the API changes. I want to test my angular service which talks with the API. How can I inject my angular service into test and get proper results if this service uses $http? Should I use jasmine, the tool for unit testing for this type of integration tests?
In this example I'm testing the OntologyService which uses $http and returns a promise, and the test looks like this:
describe('Service: OntologyService', function () {
var OntologyService, $scope;
beforeEach(function () {
module('oneClickRegistrationApp');
inject(function ($injector) {
OntologyService = $injector.get('OntologyService');
$scope = $injector.get('$rootScope').$new();
});
});
it('should return the object of ontologies', inject(function () {
var ontoServerApiUrl = 'https://myurl.com/api/ksearch/ontologies/';
OntologyService.getAllOntologies(ontoServerApiUrl).then(function (ontologies) {
expect(ontologies).toBeNonEmptyObject();
expect(ontologies["licenses"]).toHaveArrayOfObjects();
expect(ontologies["species"]).toHaveArrayOfObjects();
expect(ontologies["measurement_methods"].length).toBeGreaterThan(10);
});
$scope.$digest();
}));
});
I'm getting the following error message:
PhantomJS 1.9.8 (Mac OS X 0.0.0) Service: OntologyService should return the object of ontologies FAILED
Error: Unexpected request: GET https://myurl.com/api/ksearch/ontologies/hbp_data_modality_ontology?size=10000
No more request expected
at $httpBackend (/Users/katkov/WebstormProjects/one-click/bower_components/angular-mocks/angular-mocks.js:1323)
at sendReq (/Users/katkov/WebstormProjects/one-click/bower_components/angular/angular.js:10761)
at /Users/katkov/WebstormProjects/one-click/bower_components/angular/angular.js:10470
at processQueue (/Users/katkov/WebstormProjects/one-click/bower_components/angular/angular.js:14991)
at /Users/katkov/WebstormProjects/one-click/bower_components/angular/angular.js:15007
at /Users/katkov/WebstormProjects/one-click/bower_components/angular/angular.js:16251
at /Users/katkov/WebstormProjects/one-click/bower_components/angular/angular.js:16069
at /Users/katkov/WebstormProjects/one-click/test/spec/services/realontologyservice.js:32
at invoke (/Users/katkov/WebstormProjects/one-click/bower_components/angular/angular.js:4535)
at workFn (/Users/katkov/WebstormProjects/one-click/bower_components/angular-mocks/angular-mocks.js:2517)
undefined
PhantomJS 1.9.8 (Mac OS X 0.0.0): Executed 9 of 9 (1 FAILED) (0.016 secs / 0.158 secs)
Error: Unexpected request: GET https://
This blog post gets you covered: http://www.bradoncode.com/blog/2015/06/16/unit-test-http-ngmock-passthrough/
...$httpBackend service requires us to mock all HTTP requests used in the code under test...
...it would be nice to make a real HTTP call so that I can experiment, get some example JSON etc...
...ngMock does include ngMockE2E, which allows us to create fake backend HTTP calls, but we can only use this in the full application i.e. via the browser and not from unit tests...
Here is how to make real http request:
describe('real http tests', function() {
beforeEach(angular.mock.http.init);
afterEach(angular.mock.http.reset);
beforeEach(inject(function(_$controller_, _$httpBackend_) {
$controller = _$controller_;
$scope = {};
$httpBackend = _$httpBackend_;
// Note that this HTTP backend is ngMockE2E's, and will make a real HTTP request
$httpBackend.whenGET('http://www.omdbapi.com/?s=terminator').passThrough();
}));
it('should load default movies (with real http request)', function (done) {
var moviesController = $controller('MovieController', { $scope: $scope });
setTimeout(function() {
expect($scope.movies).not.toEqual([]);
done();
}, 1000);
});
});

Handling $http promises in your service or controller?

I find my controller gets stuffed with promise functions and my service has only got a few lines,
is good practice to handle the promise in the service?
As shown below I this is using $http.post and I've got a few more $http.get in my service as well.
// $http.post
logService.insertLog($scope.newLog)
.then(onInsertSuccess)
.catch(onError);
// $http.get
logService.getPriority()
.then(onPrioritySuccess)
.catch(onError);
var onPrioritySuccess = function (response) {
$scope.priority = response.data;
};
would it be better to have something like this?
// store the response in the scope directly?
$scope.priority = logService.getPriority();
anyway since getPriority eventually is a $http request you will have to return a promise so your controller code will look similar.
what you wrote on the top seems fine.

angular how to pass in http dependency?

trying to call an angular service from my ngapp in my jasmine script:
it('should create client', function () {
browser.executeAsyncScript(function(callback) {
var api = angular.injector(['$http','myservices']).get('custService');
api.saveClient({name:'Ted'},function(data){
console.log(data);
callback(data);
});
});
});
My question is how can I pass in the http dependency because now I am getting this error:
UnknownError: javascript error: [$injector:modulerr] Failed to instantiate module $http due to:
Error: [$injector:nomod] Module '$http' is not available! You either misspelled the module name or forgot to load it.
You should use mock data instead of creating a real object.
Doing a real http request is not a good idea.
Use mocking in the responses as #merlin said in the comments. Try to use $httpBackend to mock your response.
A quote from the documentation :
$http service sends the request to a real server using $httpBackend service
You can inject your mock there like in the example :
beforeEach(
inject(function($injector) {
// Set up the mock http service responses
$httpBackend = $injector.get('$httpBackend');
// backend definition common for all tests
authRequestHandler = $httpBackend.when('GET', '/auth.py')
.respond({userId: 'userX'}, {'A-Token': 'xxx'});
// ...
}
If you need the http service anyway, you should inject it like the following
var myHttpService;
beforeEach( inject(function($http) {
myHttpService = $http;
}));
After that you can expect a http GET for example to happen. You should call the $httpBackend.flush because in a test scenario it won't run async and $http uses promises in the background. See examples in the documentation.

$http mocking is not persistant across tests

Is this set up for $http mocking?
For some reason I am getting this error:
Uncaught Error: Unexpected request: GET http://
describe('DataService tests', function () {
var errorUrl = "/ErrorReturningURL";
var successUrl = "/SuccessReturningURL";
beforeEach(angular.mock.module('app'));
beforeEach(angular.mock.inject(function ($httpBackend) {
$httpBackend.when('GET', successUrl).respond('all good!');
$httpBackend.when('GET', errorUrl).respond(404, '');
}));
it('should call the callbackError when http returns error', inject(function (DataService, $httpBackend) {
var successCallback = jasmine.createSpy();
var errorCallback = jasmine.createSpy();
$httpBackend.expectGET(errorUrl);
DataService.getData(errorUrl, successCallback, errorCallback);
$httpBackend.flush();
expect(errorCallback).toHaveBeenCalled();
}));
}
)
;
service(simplified):
app.service('DataService', function ($http, $parse) {
this.getData = function (url, callbackSuccess, callbackError) {
$http.get(url).success( function (data) {
callbackSuccess( processedData );
}).error( function (error) {
callbackError(error);
});
};
});
original $http ?
I assume you included angular-mocks.js in your karma.js.conf file.
angular-mocks overrides the original $httpBackend , so it is impossible to do real requests.
$httpBackend mock has a synchronous API but it must integrate with your asynchronous application.
The flush() method is the connecting link between asynchronous applications and synchronous tests.
From $httpBackend docs:
Flushing HTTP requests
The $httpBackend used in production, always responds to requests with responses asynchronously. If we preserved this behavior in unit testing, we'd have to create async unit tests, which are hard to write, follow and maintain. At the same time the testing mock, can't respond synchronously because that would change the execution of the code under test. For this reason the mock $httpBackend has a flush() method, which allows the test to explicitly flush pending requests and thus preserving the async api of the backend, while allowing the test to execute synchronously
You must call flush() to actually make the request:
$httpBackend.expectGET(errorUrl);
DataService.getData(errorUrl, successCallback, errorCallback);
$httpBackend.flush();
expect(errorCallback).toHaveBeenCalled();

AngularJS $httpBackend - "No more request expected" error

It seems this is working solution that shows how to work with $httpBacked http://jsfiddle.net/EgMpe/8/
But for my case:
routes
app.config(['$routeProvider', function($routeProvider) { $routeProvider.
when('/', {templateUrl: 'partials/user-list.html'}).
...
faked service:
app.run(function($httpBackend) {
var users = [{"id":1,"name":"bob","email":"bob#bobs.com"}, {"id":2,"name":"bob2","email":"bob2#bobs.com"}]
$httpBackend.whenGET('/rest/users').respond(function(method,url,data) {
console.log("Getting users");
return [200, users, {}];
});
});
..
real service:
services.factory('Users', function($resource){
return $resource('/rest/users', {}, {
get: {method: 'GET', isArray:true}
});
});
I have error when go to my "/" route that redirects me to user-list.html page:
Error: Unexpected request: GET partials/user-list.html No more request
expected
at $httpBackend .../mysite/public/angular/libs/angular-1.2.0/angular-mocks.js:1060:9)
Question1: Does httpBackend prevent doing any other http request?
I tried to use passThrough method to let http hit real server side:
$httpBackend.whenGET(/^\/mysite\//).passThrough();
But this does not help.
Using $httpBackend you have to specify in advance all request you are going to perform. Maybe this short excerpt from Mastering Web Application Development with AngularJS will clarify why:
The verifyNoOutstandingExpectation method verifies that all the expected
calls were made ($http methods invoked and responses flushed), while the
verifyNoOutstandingRequest call makes sure that code under test didn't trigger
any unexpected XHR calls. Using those two methods we can make sure that the code
under the test invokes all the expected methods and only the expected ones.
Ah.. Sorry I just was wrong with my RegEx:
if type this $httpBackend.whenGET(/partials/).passThrough();
Then all start working.
So, I got my lesson: don't forget to put: passThrough(); with right RegEx.

Resources