Unable to use httpBackend flush for ngMockE2E - angularjs

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.

Related

$httpBackend.flush() method throws Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting

I am trying to unit test my AngularJS application using Karma and Jasmine. I want to mock the $http service. For that, I am using the $httpBackend method. Below is my service that I want to test:
angular.module('MyModule').factory('MyService', function($http, $log, $parse, $q, $timeout, $filter, MyOtherService1, MyOtherService2){
var service = {};
service.getSomething = function(id){
return $http.get('/somePath/subpath/' + id);
}
});
My unit test for this service is:
describe("myTest", function(){
var myService, $httpBackend, scope, mockMyOtherService1, mockMyOtherService2;
var myResponse =
{
foo:'bar'
};
beforeEach(module("MyModule"));
beforeEach(inject(function(_MyService_, $injector){
$httpBackend = $injector.get('$httpBackend');
myService = _MyService_;
scope = $injector.get('$rootScope').$new();
mockMyOtherService1 = $injector.get('MyOtherService1');
mockMyOtherService2 = $injector.get('MyOtherService2');
}));
beforeEach(function(){
//To bypass dependent requests
$httpBackend.whenGET(/\.html$/).respond(200,'');
});
//If I uncomment the below afterEach block, the same error is shown at next line.
/*afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});*/
//This test passes successfully
it("should check if service is instantiated", function () {
expect(myService).toBeDefined();
});
//This test passes successfully
it("should expect dependencies to be instantiated", function(){
expect($httpBackend).toBeDefined();
});
//The problem is in this test
it("should get the getSomething with the provided ID", function() {
$httpBackend.whenGET('/somePath/subpath/my_123').respond(200,myResponse);
var deferredResponse = myService.getSomething('my_123');
//The error is shown in next line.
$httpBackend.flush();
//If I comment the $httpBackend.flush(), in the next line, the $$state in deferredResponse shows that the Object that I responded with is not set i.e. it does not matches the 'myResponse'.
expect(deferredResponse).toEqual(myResponse);
});
});
This is an emergency problem and I need help regarding the same as soon as possible. I will be very grateful for your answer.
The problem was I needed to inject $location in my spec files even though they are not injected in the services. After injection, all worked well! Hope this helps someone who gets stuck in the same situation.
You will get a promise from your service. So change your test code to:
//The problem is in this test
it("should get the getSomething with the provided ID", function (done) {
$httpBackend.expectGET('/somePath/subpath/my_123').respond(200,myResponse);
var deferredResponse = myService.getSomething('my_123');
deferredResponse.then(function (value) {
expect(value.data).toEqual(myResponse);
}).finally(done);
$httpBackend.flush();
});
I've recently had this problem when updating a project from Angular 1.2 to 1.4. The test code looked something like:
it('should call /something', function(){
httpBackend.expectGET('/something').respond(200);
scope.doSomething();
httpBackend.flush();
});
The error was the infdig past 10 iterations. It was caused by invoking the .flush() method. I figured out this is seemingly because there were no pending promises created within doSomething().
Once I added a promise somewhere within doSomething() or inner methods, the infdig problem went away.
I suspect - and this is 100% speculation so don't let it influence your development - this is because httpBackend does some trickery to wait for promises, which maybe involves digesting repeatedly until there's a change. Since there's no promises, there's no changes - infinite digest.

AngularJS testing callbacks to then functions within services

I'm looking to write a Jasmine unit test which executes a callback function passed to a then function. This then function is chained to a call to the AngularJS $http service, and it's inside a custom service. Here's the code I'm working with:
app.service('myService', function($rootScope, $http) {
var service = this;
var url = 'http://api.example.com/api/v1/resources';
service.resources = {
current: []
};
service.insertResource = function (resource) {
return $http.post(url, resource).then(function(response){
$rootScope.$broadcast('resources:updated', service.resources.current);
return response;
});
};
});
Here's my attempt to write a test which executes this callback, but to no avail:
describe('resource service', function() {
beforeEach(angular.mock.module('myapp'));
var resourceService;
beforeEach(inject(function(_resourceService_) {
resourceService = _resourceService_;
}));
it('should insert resources', function() {
resourceService.insertResource({});
});
});
There are several approaches you could take:
Use $httpBackend.expectPOST
Use $httpBackend.whenPOST
Move the code in the callback to a named function (not an anonymous one) and write a test for this function. I sometimes take this route b/c I don't want the trouble of writing tests with $httpBackend. I only test the callback function, I don't test that my service is calling the callback. If you can live w/that it's much simpler approach.
Check the documentation for $httpBackend for details. Here's a simple example:
describe('resource service', function() {
beforeEach(angular.mock.module('myapp'));
var resourceService, $httpBackend;
beforeEach(inject(function($injector) {
resourceService = $injector.get('resourceService');
$httpBackend = $injector.get('$httpBackend');
}));
afterEach(function() {
// tests will fail if expected HTTP requests are not made
$httpBackend.verifyNoOutstandingRequests();
// tests will fail if any unexpected HTTP requests are made
$httpBackened.verifyNoOutstandingExpectations();
});
it('should insert resources', function() {
var data: { foo: 1 }; // whatever you are posting
// make an assertion that you expect this POST to happen
// the response can be an object or even a numeric HTTP status code (or both)
$httpBackend.expectPOST('http://api.example.com/api/v1/resources', data).respond({});
// trigger the POST
resourceService.insertResource({});
// This causes $httpBackend to trigger the success/failure callback
// It's how you workaround the asynchronous nature of HTTP requests
// in a synchronous way
$httpBackend.flush();
// now do something to confirm the resource was inserted by the callback
});
});

How to test an error response in angular's $httpBackend?

We are using angular 1.2.x (we have to due to IE8). We are testing with Karma and Jasmine. I want to test the behavior of my modules, in case the server responds with an error. According to the angular documentation, I should just simply prepare the $httpBackend mock like this (exactly as I'd expect):
authRequestHandler = $httpBackend.when('GET', '/auth.py');
// Notice how you can change the response even after it was set
authRequestHandler.respond(401, '');
This is what I am doing in my test:
beforeEach(inject(function($injector) {
keepSessionAliveService = $injector.get('keepSessionAliveService');
$httpBackend = $injector.get('$httpBackend');
$interval = $injector.get('$interval');
}));
(...)
describe('rejected keep alive request', function() {
beforeEach(function() {
spyOn(authStorageMock, 'get');
spyOn(authStorageMock, 'set');
$httpBackend.when('POST', keepAliveUrl).respond(500, '');
keepSessionAliveService.start('sessionId');
$interval.flush(90*60*1001);
$httpBackend.flush();
});
it('should not add the session id to the storage', function() {
expect(authStorageMock.set).not.toHaveBeenCalled();
});
});
But the test fails, because the mock function is being called and I can see in the code coverage that it never runs into the error function I pass to the §promise.then as second argument.
Apparently I am doing something wrong here. Could it have to with the older angular version we're using?
Any help would be appreciated!
Something like this:
it("should receive an Ajax error", function() {
spyOn($, "ajax").andCallFake(function(e) {
e.error({});
});
var callbacks = {
displayErrorMessage : jasmine.createSpy()
};
sendRequest(callbacks, configuration);
expect(callbacks.displayErrorMessage).toHaveBeenCalled();

Testing methods returning promise using jasmine

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.

AngularJS "No pending request to flush" while unit testing a controller with a $resource

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.

Resources