Mocking $httpBackend - how to handle "Unexpected request, No more request expected"? - angularjs

I have a Jasmine test that is coded like this:
it ("should send correct message to server to get data, and correctly set up scope when receiving it", function(){
$httpBackend.when('GET', 'https://localhost:44300/api/projectconfiguration/12').respond(fakedDtoBase);
$routeParams.projectId=fakeId; // user asks for editing project
scope.$apply(function(){
var controller=controllerToTest(); // so controller gets data when it is created
});
expect(scope.projectData).toEqual(fakedDtoBase);
});
and it kind of works, but I get the error:
Error: Unexpected request: GET views/core/main/main.html
No more request expected
at $httpBackend (C:/SVN/src/ClientApp/client/bower_components/angular-mocks/angular-mocks.js:1207:9)
at sendReq (C:/SVN/src/ClientApp/client/bower_components/angular/angular.js:7800:9)
at $http.serverRequest (C:/SVN/src/ClientApp/client/bower_components/angular/angular.js:7534:16)
(more stack trace)....
I do realise that I can mock every other call. But let's say I do not care what else my test wants to load as it may call few other things.
How I can make sure that every other requests just "happen silently", maybe offering a single dummy response for everything else?

Your test fails because a request is made which you haven't specified.
Try to add:
$httpBackend.when('GET', 'views/core/main/main.html').respond(fakedMainResponse);
Of course you should also define fakedMainResponse.
Please take a look also at the documentation (section Request Expectations vs Backend Definitions) which says:
Request expectations provide a way to make assertions about requests
made by the application and to define responses for those requests.
The test will fail if the expected requests are not made or they are
made in the wrong order.
The second paramete of $httpBackend.when is actually a RegExp. So if you provide a RegExp that will match all other requests it should work.

For those who are using the httpBackend to mock http calls in EndToEnd tests or just mocking the entire http calls for the application the solution is to add the following code in the app config section (change the regexp according your template's location):
$httpBackend.whenGET(/^\/templates\//).passThrough();
Reference: https://docs.angularjs.org/api/ngMockE2E/service/$httpBackend
Tested with angularjs 1.4 to fix similar problem while integrating ui-router

I think it's also important to notice that if you have a $digest(), your expectation should follow the $digest, like so:
_$rootScope_.$digest();
$httpBackend.when('GET', 'views/core/main/main.html').respond(fakedMainResponse);
// ...
$httpBackend.flush(); // And remember to flush at the end

you only need to add setTimeout and done property to your flush to prevent it
it('should get data in callback funcion', function (done) {
$httpBackend.whenGET(/\/my-endpoint/).respond(mockDataResponse);
apiFactory.getCurrencyFormat('en', function (res, err) {
expect(res.a).to.deep.equal(generalMock.a);
expect(res.b).to.deep.equal(generalMock.b);
});
setTimeout(function () {
done();
$httpBackend.flush();
}, 200);
});

Related

How to avoid the 'Error: Unexpected request: GET' every time an AngularJS test does a rootScope.digest() or httpBackend.flush()

All my UNIT tests, not E2E tests, that do an explicit rootScope.digest() or httpBackend.flush() to flush the asynchronous callback, experience the error:
How to avoid the 'Error: Unexpected request: GET'
No more request expected
I reckon it is because httpBackend calls the ui-router template. I don't know why it wants to do so. I'm not asking for this. I only want it to call my mocked json service.
This error forces me to have the following statement in each it() block:
$httpBackend.whenGET(/\.html$/).respond('');
There must be a neater way.
Specially if the test has no use of the $httpBackend in the first place:
it('should return the list of searched users', function() {
// Always use this statement so as to avoid the error from the $http service making a request to the application main page
$httpBackend.whenGET(/\.html$/).respond('');
var users = null;
UserService.search('TOTO', 1, 10, 'asc', function(data) {
users = data.content;
});
$rootScope.$digest();
expect(users).toEqual(RESTService.allUsers.content);
});
The test passes but it looks hackish. Or noobish :-)
EDIT: Another test:
it('should return the list of users', function () {
// Always use this statement so as to avoid the error from the $http service making a request to the application main page
$httpBackend.whenGET(/\.html$/).respond('');
// Create a mock request and response
$httpBackend.expectGET(ENV.NITRO_PROJECT_REST_URL + '/users/1').respond(mockedUser);
// Exercise the service
var user = null;
RESTService.User.get({userId: 1}).$promise.then(function(data) {
user = data;
});
// Synchronise
$httpBackend.flush();
// Check for the callback data
expect(user.firstname).toEqual(mockedUser.firstname);
expect(user.lastname).toEqual(mockedUser.lastname);
});
This is obviously by design, your tests should be checking that HTTP calls are being made and that they're requesting the correct URL. Instead of checking whether requests are made to /\.html$/ why not instead check whether requests are made to the correct endpoints? Whether that be a directives partial or an API call.
If you insist on throwing away what could be a useful test, you could move your whenGET() to a beforeEach().

Testing AngularJS Service with Jasmine throw an error with parsed.protocol

I'm trying to test some services that receive some modifications. Some of them are using $http service, and only one of them is populating an unknown - and a not understandable - issue.
Let me expose.
it('must reject the promise with an explanation if the required path is not found', function() {
$httpBackend.whenGET('http://localhost/testok').respond(function () {
return [200, mockedRemoteResponse, {}];
});
var promise = apiDataExtractor.extractRemoteData('ok', 'toto');
$httpBackend.flush();
});
Running this code throught Jasmine, we got this:
I do not have ANY idea of what appends. I try to change injection order, try to erase and rewrite my test, there is something here I'm missing.
Can anyone help?
The error you are seeing is probably due to a call to $http.get with undefined as the url (you might have forgotten to provide parameters to $http.get() inside apiDataExtractor.extractRemoteData?)
Try debugging your code and see what are the parameters you are giving the $http.get method.

$httpBackend handling more than 3 calls

I am currently running angularjs 1.2.10 and using karma/jasmine with angular-mocks-1.2.10 for unit testing and stuck in unit test case for $httpBackend.
inside it block
describe('sumCtrl'...)
...
beforeEach(angular.mock.inject(function($rootScope,$controller,$httpBackend){
scope = $rootScope.$new();
httpBackend = $httpBackend;
$controller('sumCtrl',{$scope:scope});
}));
it("should call these http services",function(){
httpBackend.expectGET('/api/something1/').respond({success:true});
httpBackend.expectPOST('/api/something2/').respond({success:true});
httpBackend.flush();
});
The above code works perfectly but when I add one more httpBackend call
it("should call these http services",function(){
httpBackend.expectGET('/api/something1/').respond({success:true});
httpBackend.expectPOST('/api/something2/').respond({success:true});
httpBackend.expectPOST('/api/something3/').respond({success:true});
httpBackend.flush();
});
This gives error on line 4. Unsatisfied Request: POST '/api/something3' .... use $httpBackend....
Don't know whether there is a limit to number of requests made using $httpBackend in it block or something else that needs to be kept in mind while using $httpBackend.
I made a fiddle that should you can have more than 2: http://fiddle.jshell.net/gZS5M/
$http.get('/api/something1');
$http.post('/api/something2');
$http.post('/api/something3');
$http.post('/api/something4');
Works fine. I'm guessing you just have a typo in your url or something.
Feel free to shoot back at me if this doesn't help

Karma: Check not flushed requests

I write tests for angular app using karma, is any way i can check not flushed requests before $httpBacked.flush()? i need this because i dont know is request sent or not and if no, $httpBacked.flush() cause exception.
i need something like this:
it('should', function() {
if($httpBackend.isRequestsPresent()) {
$httpBackend.flush();
}
})
You can use
$httpBackend.verifyNoOutstandingExpectation()
$httpBackend.verifyNoOutstandingRequest()
to check on the requests send/not or expectations met/not met sent.
See also here.

AngularJS Execute function after a Service request ends

I am using AngularJS Services in my application to retrieve data from the backend, and I would like to make a loading mask, so the loading mask will start just before sending the request. but how can I know when the request ends?
For example I defined my servive as:
angular.module('myServices', ['ngResource'])
.factory('Clients', function ($resource) {
return $resource('getclients');
})
.factory('ClientsDetails', function ($resource) {
return $resource('getclient/:cltId');
})
So I use them in my controller as:
$scope.list = Clients.query();
and
$scope.datails = ClientsDetails.get({
date:$scope.selectedId
});
So the question would be, how to know when the query and get requests ends?
Edit:
As a side note in this question I've been using using angularjs 1.0.7
In AngularJS 1.2 automatic unwrapping of promises is no longer supported unless you turn on a special feature for it (and no telling for how long that will be available).
So that means if you write a line like this:
$scope.someVariable = $http.get("some url");
When you try to use someVariable in your view code (for example, "{{ someVariable }}") it won't work anymore. Instead attach functions to the promise you get back from the get() function like dawuut showed and perform your scope assignment within the success function:
$http.get("some url").then(function successFunction(result) {
$scope.someVariable = result;
console.log(result);
});
I know you probably have your $http.get() wrapped inside of a service or factory of some sort, but you've probably been passing the promise you got from using $http out of the functions on that wrapper so this applies just the same there.
My old blog post on AngularJS promises is fairly popular, it's just not yet updated with the info that you can't do direct assignment of promises to $scope anymore and expect it to work well for you: http://johnmunsch.com/2013/07/17/angularjs-services-and-promises/
You can use promises to manage it, something like :
Clients.query().then(function (res) {
// Content loaded
console.log(res);
}, function (err) {
// Error
console.log(err);
});
Another way (much robust and 'best practice') is to make Angular intercepting your requests automatically by using interceptor (see doc here : http://docs.angularjs.org/api/ng.$http).
This can help too : Showing Spinner GIF during $http request in angular
As left in a comment by Pointy I solved my problem giving a second parameter to the get function as following:
$scope.datails = ClientsDetails.get({
date:$scope.selectedId
}, function(){
// do my stuff here
});

Resources