AngularJS ngMock call $httpbackend.flush for nested $http - angularjs

I need to write a unit test for service A, which depends on service B. Service B makes an $http calls sometimes. How can I properly call $httpBackend.flush() to get those results.
Here is the basic idea of what have:
serviceB.someMethod = function(){
var deferred = $q.defer();
someOtherCall()
.then(function(data){ deferred.resolve(data) })
.catch(function(err){
$http.get(options).success(function(data){deferred.resolve(data)}); // <-- HTTP call
});
return deferred.promise;
};
serviceA = function(things, options, filterFn){
var promises = [],
angular.forEach(things, function(thing){
promises.push(filterFn(thing, options)); // <-- filterFn calls serviceB.someMethod
});
return $q.all(promises);
};
How can I determine in my jasmine test when to call $httpBackend.flush(), so that serviceB will resolve, and thus service A? Obviously my real services have more going on, but these example should demonstrate the problem.

Related

Mock $http with configuration parameters

I'm applying some tests in an existing AngularJS application in order to ensure it's correct behaviour for future changes in the code.
I am pretty new with Jasmine & Karma testing, so I've decided to start with a small and basic service which performs an http request to the backend, and waits for the result with a promise, nothing new.
Here's the service method to test:
function getInformedConsent(queryParameters) {
var def = $q.defer(),
httpParameters = {
url: ENV.apiEndpoint + '/urlResource',
method: 'GET',
params: queryParameters,
paramSerializer: '$httpParamSerializerJQLike'
};
$http(httpParameters)
.then(
function (response) {
def.resolve(response);
},
function (error) {
def.reject(error);
}
);
return def.promise;
}
And here my test:
it('getInformedConsent method test', function() {
$httpBackend.expectGET(/.*\/urlResource?.*/g)
.respond(informedConsentJson.response);
var promise;
promise = InformedconsentService.getInformedConsent(informedConsentJson.queryParameters[0]);
promise
.then(function(response) {
console.log(response);
expect(response).toEqual(informedConsentJson.response);
});
$httpBackend.flush();
});
informedConsentJson as you can supose, is a fixture with input and the expected output.
Reading AngularJS documentation, I decided to use $httpBackend, because it's already a mock of $http service, so I thought it could be useful.
The problem is that somewhere in the code, someone is broadcasting a "$locationChangeStart" event and executing
$rootScope.$on('$locationChangeStart', function (event,current,old) {
/* some code here */
});
in app.js.
I'm not trying to change the URL, i'm just trying to get some data from the mocked backend.
I asume that is because I'm not using $http mock ($httpBackend) as it should be used.
Anyone can help me with $http with configuration JSON mock?
It's freaking me out.
Thank you all in advance for your time and your responses

Mock angular service data response

I have an Api service which is in charge of controlling all my http requests. GET, POST, PUT, DELETE...
I'm trying to write some unitTests and I get a problem with the following scenario.
self.Api.post('/myEndpoint/action/', actionData)
.then(function(resp){
result = _.get(resp, 'data.MessageList');
if(resp.status = 200 && result) {
setActionResults(resp.data);
}
});
I want to mock in my unitTest the resp. What should I do? Must I mock the httpBackend service as here http://plnkr.co/edit/eXycLiNmlVKjaZXf0kCH?p=preview ? Can I do it in other way?
Using httpBackend is the way to go, mocking each request made by your application will work just fine. However you can mock your entire service as well, and unit test using the mocked service instead of the original. Regardless, httpBackend is much more simple to handle that (for http request services) than creating a new service with the same interface of the original. But in some case, you may need to control what your services are doing, therefore you will have to use service mocking.
For example:
angular.module('myApp')
.service('DataService', function ($http) {
this.getData = function () {
return $http.get('http://my.end.point/api/v1/data')
.then(function (response) {
return response.data;
});
};
});
angular.module('myAppMock')
.service('MockedDataService', function ($q) {
this.getData = function () {
return $q.resolve({ data: 'myData' }); // you can add a delay if you like
}
});

$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();

Angular 1.1.5 test promise-based service

We use karma to unit test our angular services, these services contains $http calls, so we have a mocked $httpbackend in place so we can run the app without server and db.
this works fine, a service can call $http("someurl?id=1234") and we get the right data back.
But when we try to do the same thing in unit tests, we can't get it to work, the promise never resolves, when it involves $http
The service:
getAllowedTypes: function (contentId) {
var deferred = $q.defer();
$http.get(getChildContentTypesUrl(contentId))
.success(function (data, status, headers, config) {
deferred.resolve(data);
}).
error(function (data, status, headers, config) {
deferred.reject('Failed to retreive data for content id ' + contentId);
});
return deferred.promise;
}
The mocked $httpbackend
$httpBackend
.whenGET(mocksUtills.urlRegex('/someurl'))
.respond(returnAllowedChildren); //returns a json object and httpstatus:200
The test
it('should return a allowed content type collection given a document id', function(){
var collection;
contentTypeResource.getAllowedTypes(1234).then(function(result){
collection = result;
});
$rootScope.$digest();
expect(collection.length).toBe(3);
});
but collection is undefined, .then() is never called.
tried pretty much everything to get the promise to resolve, $rootScope.$apply(), $digest, $httpBacke.flush(), but nothing works
So mocked $httpBackend works when called from controllers in app, but not when services is called directly in karma unit tests
You should not need to digest twice, since $httpBackend.flush() calls digest itself.
You have to make the call, call digest to resolve the request interceptors, the call flush.
Here is a working Plnkr: http://plnkr.co/edit/FiYY1jT6dYrDhroRpFG1?p=preview
In your case, you have to $digest twice, once for $httpBackend, and again for your own deferred.
So:
it('should return a allowed content type collection given a document id', function(){
var collection;
contentTypeResource.getAllowedTypes(1234).then(function(result){
collection = result;
});
$httpBackend.flush();
$rootScope.$digest();
expect(collection.length).toBe(3);
});
You're almost there. In your case, you just need to force a digest cycle before flushing the HTTP backend. See sample code below.
it('should return a allowed content type collection given a document id', function(){
var collection;
contentTypeResource.getAllowedTypes(1234).then(function(result){
collection = result;
});
$rootScope.$digest();
$httpBackend.flush();
expect(collection.length).toBe(3);
});

How to verify that an http request is not made at all?

how to verify that none of http request method are invoked to do any request. I have this code :
$scope.getSubnetsPageDetails = function (pageNumber) {
$http.get(URLS.subnetsPagesCount(pageNumber)).success(function (response) {
$scope.pageDetails = response;
}).error(function (response, errorCode) {
});
};
and this test :
it("should not allow request with negative page number", function () {
scope.getSubnetsPageDetails(-1);
//verify that htt.get is not invoked at all
});
How to verify that http.get is not invoked ?
You can test that no calls are made by using the verifyNoOutstandingRequest() method from the $httpBackend mock.
Usually those kind of verification is done in the afterEach section of a Jasmine's tests. On top of this it is common to call another method, verifyNoOutstandingExpectation() to verify that all the expected calls were actually invoked.
Here is the code, where you need to inject the $httpBackend mock:
var $httpBackend;
beforeEach(inject(function($injector) {
$httpBackend = $injector.get('$httpBackend');
}));
then do you test and at the end:
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
Of course you could invoke the $httpBackend.verifyNoOutstandingRequest() inside an individual test. The mentioned http://docs.angularjs.org/api/ngMock.$httpBackend page has a wealth of information on the topic.

Resources