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.
Related
I'm trying to wrap my head around interceptors, I still can't figure them out. Can someone explain a tad more for me about if they are a service, a config, etc?
Interceptors can be either a named factory or an anonymous factory.
app.config(function ($httpProvider) {
//register the interceptor factory
$httpProvider.interceptors.push('myHttpInterceptor');
// alternatively, register the interceptor via an anonymous factory
$httpProvider.interceptors.push(function($q, dependency1, dependency2) {
return {
'request': function(config) {
// request transform
},
'response': function(response) {
// response transform
}
};
});
});
Interceptors are registered during the config phase of the AngularJS app. Their factory functions (either named or anonymous) are invoked during the $get phase of the $http service.
For more information, see AngularJS $http Service API Reference - Interceptors
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);
});
});
HowTo properly test Angular's $http success and error promise with sinon.js? This is what I have so far (all in coffeescript):
Service Autobahn
drive: (vehicle) ->
$http.post '/user/signin', vehicle
.success ((data) ->
# do stuff
return data
).error
throw Error (error)
Controller Streets
$scope.drive = (vehicle) ->
$scope.data = Autobahn.drive(vehicle)
Unit Test w. Sinon
it 'should test $http callbacks', ->
sinon.stub($scope, 'drive').yieldsTo 'success', callBackMockData
$scope.drive vehicle, (data) ->
expect($scope.data).to.be(callBackMockData)
return
return
Result
TypeError: 'undefined' is not a function (evaluating '(function(data) {
return data;
}).error(function(data) {
throw Error(error);
})')
Maybe stub is not the correct approach here as I want to really test the controller and service in one go. How can we test Angular's $http promises? The offical documentation uses httpBackEnd failing to provide details on how to test $http's callback chain.
Don't use $http's success and error promises. $http is a promise by itself. Use .then().
See: Undefinied is not a function when using .success on a function that returns a promise
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.
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();