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.
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 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
}
});
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
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 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.