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);
});
Related
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
I have a service that calls a REST URL that returns data.
My sample code would be
$http('POST', '/mockUrl/resource/'+resourceValue).then(...);
My service works fine and it returns data. My problem is how do I test this in karma. Right now I have a different resourceValue to be tested for the mockUrl being called. Before coming to stackoverflow, in each test i was defining $httpBackend with the expected URL.
eg:
it('testing for status 200', function(){
$httpBackend.when('POST', '/mockUrl/resource/'+1234)
.respond(function (method, url, data, headers) {
return [200, data1];
});
MyService.serviceMethod(1234).then(function(){
//test the returned data
});
});
it('testing for status 201', function(){
$httpBackend.when('POST', '/mockUrl/resource/'+4567)
.respond(function (method, url, data, headers) {
return [201, data2];
});
MyService.serviceMethod(1234).then(function(){
//test the returned data
});
});
I am being told that I should not be writing my karma tests in the above manner. But I am not sure how to avoid that.
I have tried
$httpBackend.when('POST',url)
.respond(function (method, url, data, headers) {
return data[option];
});
But this 'url' never gets called in any test. I am not sure how to proceed further.
it seems that you never call $httpBackend.flush(); that is needed to simulate async operations... Plus, you also never use
afterEach(() => {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
Probably you need to have a more in-deep view at https://docs.angularjs.org/api/ngMock/service/$httpBackend
I'm trying to write a test in Protractor/Jasmine that depends upon my being able to see the headers sent in an HTTP request. To that end I'm trying to create a mock endpoint with $httpBackend that will respond to a call with the headers themselves, allowing me to look into them in my test. My code is as follows:
describe('Headers', function () {
it('should include X-XSRF-TOKEN on HTTP calls', function () {
browser.addMockModule('httpBackendMock', function () {
angular.module('httpBackendMock', ['CorsApp', 'ngMockE2E'])
.run(function ($httpBackend) {
$httpBackend.whenGET('/xsrftest')
.respond(function (method, url, data, headers) {
return headers;
});
})
});
loginPage.get();
browser.executeAsyncScript(function (callback) {
var $http = angular.injector(['ng']).get('$http');
$http.get('/xsrftest')
.then(function (response) {
callback(response);
});
})
.then(function (response) {
console.log(response);
});
});
});
I've tried to follow the patterns set out in many resources for utilizing $httpBackend in protractor testing. However, when I run this test, I get a Protractor timeout. It seems as though the $http.get call never receives a response, hence the callback is never called and so the executeAsyncScript call times out. If I put in a dummy call to the callback that's not dependent on the $http.get, it works as expected.
What am I doing wrong in setting up $httpBackend? How can I get it to respond to my $http request?
Thanks!
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.
My resource looks something like this.
return $resource(baseURL + 'user',{},{
isPermitted: {method: 'POST', isArray:false, params: { regID: #regID} },
doesExist: {method: 'GET', url: baseURL + 'user/doesExist' }
});
I have written Jasmine tests for the same.
What I am trying to understand is
Is this the correct way to layout the test (or should i be using something like sinon)
Are these the only test that need to be performed on a resource (or should i be writing a lot more tests. Kindly point out to what other aspects need to be tested)
The test:
describe('UserCheck',function(){
var $httpBackend, mockUserCheckResource;
var webServiceBaseURL = 'server.comp.com';
beforeEach(module('demo'));
beforeEach(function(){
angular.mock.inject(function($injector){
$httpBackend = $injector.get('$httpBackend');
mockUserCheckResource = $injector.get('UserCheck');
});
});
afterEach(function(){
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
describe('isPermitted',function(){
var aObj = {regID:'xxx'};
it('should issue a POST request to /user',function(){
var result;
$httpBackend.expectPOST(webServiceBaseURL + 'user',{regID:'xxx'}).respond(201);
result = mockUserCheckResource.isPermitted(aObj);
$httpBackend.flush();
expect(result.regID).toBeDefined('xxx');
});
});
describe('doesExist',function(){
it('should issue a GET request to /user/doesExist',function(){
var result = {};
$httpBackend.expectGET(webServiceBaseURL + 'user/doesExist?userID=123').respond({"isPresent":1});
result = mockUserCheckResource.doesExist({userID:'123'});
$httpBackend.flush();
expect(result.isPresent).toBe(1);
});
});
);
Two things here : you can test whether $resource properly maps whatever instructions it is given to HTTP calls, which is kind of pointless, and you can also test that your application has a service called UserCheck which maps two methods (isPermitted and doesExist) to the proper HTTP calls. These are different. For the latter — which definitely does make sense — your tests are allright (and offer a good coverage of what your code does in terms of inputs (method calls, HTTP requests), not how it does it.
Still, you can simplify a bit and make your tests clearer :
describe('UserCheck',function(){
var $httpBackend = undefined;
var UserCheck = undefined;
beforeEach(module('demo'));
beforeEach(inject(function(_$httpBackend_, _UserCheck_) {
$httpBackend = _$httpBackend;
UserCheck = _UserCheck_;
}));
afterEach(function(){
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
describe('isPermitted',function(){
it('should issue a POST request to /user',function(){
$httpBackend.expectPOST('server.comp.com/user').respond(200);
UserCheck.isPermitted({});
$httpBackend.flush();
});
});
describe('doesExist',function(){
it('should issue a GET request to /user/doesExist',function(){
$httpBackend.expectGET('server.comp.com/user/doesExist?userID=123').respond(200);
UserCheck.doesExist({userID:'123'});
$httpBackend.flush();
});
});
});
Tips :
name your services the same in your code and in your tests
don't specify the values returned by $httpBackend unless you want to test how your service/controller will handle them
always use plain text URL in your $httpBackend's expectations : it allows to quickly map a method and an URL, and also allows to search across the test (like 'where does this kind or URL gets called ?')
no global state, even in tests (except for injected dependencies). If your test grows bigger, it'll be a pain to refactor and handle several different use cases, which is basiclaly the purpose of a unit test file.