I am trying to use $httpBackend to test my $http request ..i AM getting
this error
Unexpected request: GET data.json
No more request expected
here is my testing code
beforeEach(inject(function($rootScope,$controller,appfactory,_$httpBackend_) {
$scope = $rootScope.$new();
$httpBackend=_$httpBackend_;
ctrl = $controller('cntrl', {$scope: $scope});
fac=appfactory;
modeSpy= spyOn(fac, 'mode').and.returnValue('a');
}));
it('test true value',function(){
expect(true).toBeTruthy()
})
it('check message value',function(){
$scope.toggle();
expect($scope.message).toEqual('naveen')
})
it("tracks that the spy was called", function() {
//expect(fac.setValue).toHaveBeenCalled();
var response=[{"name":"naveen"},{"name":"parveen"}]
$httpBackend.expectGET('data.json').respond(response);
$scope.getData();
$httpBackend.flush();
expect($scope.data[0].name).toEqual('naveen')
});
here is my code
http://plnkr.co/edit/zdfYdtWbnQz22nEbl6V8?p=preview
Solution to remove this error
I have this controller
.controller('cntrl',function($scope,appfactory,$http){
$scope.data=[];
appfactory.setValue('test abc');
$scope.getData=function(){
$http.get('data.json').success(function(data){
console.log(JSON.stringify(data))
$scope.data=data;
}).error(function(data){
console.log(JSON.stringify(data))
})
}
$scope.getData();
$scope.toggle=function(){
if(appfactory.mode()=='a'){
$scope.message='naveen'
}else {
if(appfactory.mode()=='b'){
$scope.message='parveen'
}
}
}
})
if i comment out this line $scope.getData(); or remove this line $scope.getData(); then my test is pass and I am able to remove this error .
My Question why this error occurred? If am not able to use the function in controller then what is the use of testing of this function ?
When initialising the controller because you're calling $scope.getData(), data.json will be fetched. So, when initialising the controller in test, that request would have to be mocked every time controller is initialised.
One way to overcome this is to add ng-init="getData()" to the html element using this controller, so that the data would be fetched but only on request by the html and not when controller is initialized.
Related
I am trying to test my controller using jasmine. Basically, when the controller is created it will call a service to make http request. I am using httpBackend to get the fake data. When I try to run the test I always get the error "No pending request to flush". If I remove the httpBackend.flush() then the test fails because controller.data.name is undefined. Can anyone know why it happens like that? Thanks.
The code for the module is here:
var myModule = angular.module('myModule', ['ngMockE2E']);
myModule.run(function($httpBackend){
$httpBackend.whenGET('/Person?content=Manager').respond(function (){
var response = {'name':'Bob','age':'43'}
return [200,response];
})
});
The code for the service:
myModule.factory('myService',function($http){
return {
getData: function(position){
return $http.get('/Person?content='+position);
}
}
});
The code for controller is:
myModule.controller('myController',function(xrefService){
var _this = this;
_this.data ={};
_this.getData = function(position){
myService.getData(position).then(function(response){
_this.data = response.data
});
}
_this.getData("Manager");
})
The code to test the controller is:
describe("Test Controller",function(){
var controller,httpBackend,createController;
beforeEach(module('myModule'));
beforeEach(inject(function($controller,$httpBackend){
createController = function(){
return $controller('myController');
}
httpBackend = $httpBackend;
}));
it("should return data",function(){
controller = createController();
httpBackend.flush();
expect(controller.data.name).toEqual("Bob");
});
})
The angular documentation says the following about $httpbackend for ngMockE2E:
Additionally, we don't want to manually have to flush mocked out
requests like we do during unit testing. For this reason the e2e
$httpBackend flushes mocked out requests automatically, closely
simulating the behavior of the XMLHttpRequest object.
So, short answer: it doesn't exist and you don't need it.
you are using $httpBackend.whenGET inside "The code for the module"
you should be using $httpBackend inside the test code as follows ...
it("should return data",function(){
$httpBackend.expectGET('/Person?content=Manager').respond(function (){
var response = {'name':'Bob','age':'43'}
return [200,response];
})
controller = createController();
httpBackend.flush();
expect(controller.data.name).toEqual("Bob");
});
also i would advise using expectGET instead of whenGET.
With whenGET you are saying if the request is made then response like so.
With expectGET you are saying ... a request will be made, when it is made respond like so, if the request is not made then fail the test.
PS if you put some console.log statements inside your controller code then you should see these log statements when you run your test suite. If not then you know your controller code is not even being hit.
also use ..
afterEach(function () {
httpBackend.verifyNoOutstandingExpectation();
httpBackend.verifyNoOutstandingRequest();
});
which will force test failure if expectations were not met.
This question already has an answer here:
Unexpected request: GET No more request expected at $httpBackend
(1 answer)
Closed 7 years ago.
I am trying to make a demo of $http request and test that service also..I do like this
.controller('cntrl',function($scope,appfactory,$http){
$scope.data=[];
appfactory.setValue('test abc');
$scope.getData=function(){
$http.get('data.json').success(function(data){
console.log(JSON.stringify(data))
$scope.data=data;
}).error(function(data){
console.log(JSON.stringify(data))
})
}
$scope.getData();
$scope.toggle=function(){
if(appfactory.mode()=='a'){
$scope.message='naveen'
}else {
if(appfactory.mode()=='b'){
$scope.message='parveen'
}
}
}
})
I load that data from json file and display it on view..But when I try to test that application ..I got error why ? how to remove that error
beforeEach(inject(function($rootScope,$controller,appfactory,_$httpBackend_) {
$scope = $rootScope.$new();
$httpBackend=_$httpBackend_;
createController = function() {
return $controller('cntrl', {'$scope' : $scope });
};
}));
it("tracks that the spy was called", function() {
var response=[{"name":"naveen"},{"name":"parveen"}]
$httpBackend.expectGET('data.json').respond(response);
var controller = createController();
$scope.getData();
$httpBackend.flush();
expect($scope.data[0].name).toEqual('naveen')
});
})
here is my code
http://plnkr.co/edit/zdfYdtWbnQz22nEbl6V8?p=preview
http call to get data is effectively getting called twice in the test. One is when the controller is initialized (because you're calling $scope.getData()) and the second is when you manually call $scope.getData() in the spec.
And your expectation is only mocked once (before controller initialisation). To fix the test (to mimic what the controller is actually doing), the expectation has to be set once more before $scope.getData() is called.
See plunker: http://plnkr.co/edit/eLPOZTCgkSfoACLJsVZo?p=preview
OR
You can replace the expectGET with whenGET which doesn't fail the test if the resource is not called. BUT since you're expecting expect($scope.data[0].name).toEqual('naveen') where 'naveen' comes from the http respose, that change should be fine.
The following test passes:
admin.controller.js
angular
.module('mean-starter')
.controller('AdminController', AdminController);
function AdminController(User, Auth, $state) {
var vm = this;
User
.list()
.success(function(data) {
vm.users = data;
})
.error(function() {
console.log('Problem getting users.');
});
vm.delete = function(id) {
User
.delete(id)
.success(function(data) {
if (Auth.getCurrentUser()._id === id) Auth.logout(); // deleting yourself
else $state.reload();
})
.error(function() {
console.log('Problem deleting user.');
});
};
}
admin.controller.spec.js
describe('AdminController', function() {
var AdminController, $httpBackend;
beforeEach(module('mean-starter'));
beforeEach(module('templates'));
beforeEach(inject(function($controller, $rootScope, _$httpBackend_) {
$httpBackend = _$httpBackend_;
AdminController = $controller('AdminController');
}));
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it('gets users', function() {
$httpBackend
.expectGET('/users')
.respond('foo');
$httpBackend.flush();
});
});
I wouldn't expect it to. Here is what I expected to happen:
The controller is instantiated in the beforeEach.
User.list() gets run.
The $http isn't yet overridden by $httpBackend, so the request goes out normally.
$httpBackend.expectGET('/users').respond('foo') expects GET /users. And says, "I'll respond with 'foo' if I get that request".
$httpBackend.flush() says "Send out the defined responses for any of the requests that $httpBackend received."
.expectGET fails because it doesn't receive it's request (the request happened before the expectation).
.flush() throws an error because there's nothing to flush.
I'm not getting the outcomes I was expecting, so something about my logic above must be wrong - what is it?
The $http isn't yet overridden by $httpBackend, so the request goes out normally.
This is not a correct assumption. $httpBackend is used automatically in unit tests (it's part of the ng-mocks module that's used in unit tests). So whether you use $httpBackend or not in your unit test code, it's there and it's processing all the $http requests that your code makes.
Think about it, if it wasn't doing this, your unit tests could make real requests.
EDIT
For cases like this, where the controller is making an HTTP request as soon as it's instantiated, I like to put the $httpBackend.expectGET() call in the beforeEach block right before you instantiate the controller.
I would also flush() the backend in the beforeEach block as well. This, I think, makes it clear that these requests happen at controller startup. And it means I don't have to make this expectation in every unit test.
I am using jasmine & karma to test my angular app. I have a service as follows
app.service('demo1', function( $http ){
this.send = function(){
return $http({
url: 'someurl'
});
}
});
The response is mocked using ngMockE2E.
My jasmine spec is as follows:
describe('Testing asynchronus', function(){
var demoService;
beforeEach(function(){
module('app');
inject(function( demo1 ){
demoService = demo1
});
});
it('Should be able to test promise', function(){
demoService.send().then(function( data ){
expect(data.status).toBe(true);
});
});
});
Now the problem is, the expect is not executing. the test being passed every time, no matter what the value of data.status is. I need help on how to test these kinds of scenarios? Thanks in advance.
Real code:
describe("Testing MetaService", function(){
var _entityMeta_, metaService, scope;
beforeEach(function(){
console.log( '---------------------- Starting Meta Service fetchEntityMeta Test ---------------------------' );
module(APP_MODULE_NAME);
inject(function(_entityMeta_, _metaService_, $rootScope){
metaService = _metaService_;
entityMeta = _entityMeta_;
scope = $rootScope.$new();
});
});
afterEach(function(){
console.log( '---------------------- Ending Meta Service fetchEntityMeta Test ---------------------------' );
});
// Giving mock data from entityMeta.person as input
it("Should have a valid structure", function($rootScope){
console.log( '////////////////////////////////////////////' );
metaService.fetchEntityMeta('person').then(function( data ){
console.log( data );
expect(data.type).toBe('object');
expect(data.properties.length).toBeGreaterThan(0);
expect(data.definitions.length).toBeGreaterThan(0);
});
});
});
I am getting following error:
Error: Timeout - Async callback was not invoked within timeout specified
by jasmine.DEFAULT_TIMEOUT_INTERVAL.
try to add
beforeEach(module('app'));
under the describe
If you're using Jasmine 2, the argument to the callback passed to it is supposed to be a done function called when the test finished. (The test runner will time out if it is never called.)
(See the official doc here, or one of many blog posts.)
I don't really understand why you pass $rootScope in this case, but the error message looks like a complain because the done function (which happens to be named $rootScope here) has not be called.
This might work :
// Giving mock data from entityMeta.person as input
it("Should have a valid structure", function(done){
console.log( '////////////////////////////////////////////' );
metaService.fetchEntityMeta('person').then(function( data ){
console.log( data );
expect(data.type).toBe('object');
expect(data.properties.length).toBeGreaterThan(0);
expect(data.definitions.length).toBeGreaterThan(0);
done();
}, function (e) {
// The promise was not resolved, this is most likely an
// implementation error. Let's fail the test !
throw new Error(e);
});
});
If the then part is never called, the done function will not either, an you'll get the timeout error you're seeing right now.
I am writing unit tests for a controller. This controller has a $resource service injected :
function controller($scope, Service) {
Service.get(function(result){
// do stuff with the result, not relevant here
}
}
The service is defined like this :
angular.module('so').factory('Service', ['$resource', service]);
function service($resource) {
return $resource('/url', null, {
get: { method: 'POST', params: {}, isArray: false}
});
}
My Jasmine unit test is the following :
describe("Controller", function(){
var $httpBackend;
beforeEach(function() {
module('so');
inject(function( _$httpBackend_) {
$httpBackend = _$httpBackend_;
});
});
it('should have done stuff irrelevant to the question', function() {
var $injector = angular.injector('so'),
$scope = $injector.get('$rootScope'),
$httpBackend
.whenPOST('/url')
.respond ([]);
// controller needs to be defined here and not in the beforeEach as there
// are more parameters passed to it, depending on the test
var controller = $injector.get('$controller')('controller', { "$scope": $scope });
$httpBackend.flush();
// then here the actual test resolution, also irrelevant
});
});
I get an error when running the test :
Error: No pending request to flush ! in file:///path/to/angular-mock.js (line 1453)
I added a console.log() in the callback from Service.get() and indeed, it is not called (everything outside of the callback is of course called). Also tried to add a scope digest if not phased after controller creation in the unit test, as I saw suggested in an other question, with no luck.
I know that I can mock that in some other ways, but using $httpBackend seems the perfect solution for the test : mocking the webserver and the data received.
I'm using AngularJS 1.2.16 (can't upgrade to 1.3.*, IE 8 compatibility required). I first used 1.2.13 and updated to check if it would solve the issue, without any luck.
That was an injection issue that was solved by changing the test from
it('should have done stuff irrelevant to the question', function() {
var $injector = angular.injector('so'),
$scope = $injector.get('$rootScope'),
$httpBackend
.whenPOST('/url')
.respond ([]);
// controller needs to be defined here and not in the beforeEach as there
// are more parameters passed to it, depending on the test
var controller = $injector.get('$controller')('controller', { "$scope": $scope });
$httpBackend.flush();
// then here the actual test resolution, also irrelevant
});
To:
it('should have done stuff irrelevant to the question', inject(function(Service) {
// edited lines because they did not change
var controller = $injector.get('$controller')('controller', { "$scope": $scope, "Service": Service });
// edited lines because they did not change
}));
So basicaly, adding the inject() in the test function and passing the service to the controller "manually".
I found the issue, that's great, but I don't really understand why it doesn't work. Also, I tried this right after finding the solution :
it('should have done stuff irrelevant to the question', inject(function() {
// edited lines because they did not change
var Service = $injector.get('Service'),
var controller = $injector.get('$controller')('controller', { "$scope": $scope, "Service": Service });
// edited lines because they did not change
}));
but this fail again, with the same "no pending request" error. I'm guessing that's some sort of racing issue, where my service can't get the proper $httpBackend to be injected when it's created afterwards, but I don't really understand why this occurs. If anybody can enlighten me... I'll be grateful.