Attempting to mock an http call but getting the error
Error: Unflushed requests: 1 in /Users/..etc/angular-mocks.js
here is my relevant code
describe('Tests for Group Controller', function() {
var $httpBackend;
beforeEach(function() {
module('App');
return inject(function($injector) {
var $controller;
$window = $injector.get('$window');
$window.ENV = 'http://here.com'
this.rootScope = $injector.get('$rootScope');
this.state = {};
this.stateParams = {};
this.UserService = $injector.get('UserService');
this.ProjectService = $q = $injector.get('ProjectService');
this.ModalService = {};
$httpBackend = $injector.get('$httpBackend');
this.GroupService = {};
$q = $injector.get('$q');
this.q = $q;
$controller = $injector.get('$controller');
this.scope = this.rootScope.$new();
this.controller = $controller('GroupController', {
'$scope': this.scope,
'$state': this.state,
'$stateParams': this.stateParams,
"UserService": this.UserService,
'ProjectService': this.ProjectService,
'ModalService': this.ModalService,
'GroupService': this.GroupService,
'$q': this.q
});
// this.scope.$digest();
$httpBackend.when('GET', 'http://here.com/api/user/2/Group/list?offset=0&max=10&sortField=name&ascending=true').respond({data: 'success'});
});
});
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it('should have controller defined', function() {
expect(this.controller).toBeDefined()
});
it('should initGroups', function() {
$httpBackend.expectGET('http://here.com/api/user/2/Group/list?offset=0&max=10&sortField=name&ascending=true');
$httpBackend.flush();
})
});
I'm doing the flush after the expectget but it still says I have an unflushed request. What am i doing wrong?
You create GET request before each test, but flush it only in 'Should initGroups' test.
Related
I wrote some unit tests for an angular factory that mock the response with a fake promise using $q.resolve({}). I'm now writing the controller test and want to call the factory from the controller and assign the resolved mock factory response to the controller scope (vm), and verify the data. Is this the proper way to test a factory from within a controller?
factory test
describe('getVICs()', function() {
var vicFactory, $q, $httpBackend, result;
beforeEach(module('vicModule'));
beforeEach(inject(function(_vicFactory_, _$q_, _$httpBackend_) {
vicFactory = _vicFactory_;
$q = _$q_;
$httpBackend = _$httpBackend_;
}));
beforeEach(function() {
result = {};
spyOn(vicFactory, 'getVICs').and.callThrough(function() {
var deferred = $q.defer();
return deferred.promise;
});
});
it('should be defined', function() {
expect(vicFactory.getVICs()).toBeDefined();
});
it('should return list of vics', function() {
var vicList = {
"results": [
{
"version": 5
},
{
"version": 1
}
]
};
var qs = '?sort=version&dir=DESC&page=1&limit=10&status=ACTIVE';
$httpBackend.expectGET('/path/to/my/api' + qs).respond(200, vicList);
$httpBackend.whenGET('http://localhost:3000/path/to/my/api' + qs).respond(200, $q.when(vicList));
vicFactory.getVICs(qs)
.then(function(response) {
result = response;
});
// flush pending HTTP requests
$httpBackend.flush();
expect(result.data.results).toBeDefined();
});
});
factory
function getVICs(query) {
return $http.get('/path/to/my/api' + query)
.then(function(response) {
return response;
});
}
controller test
describe('vicController', function() {
var $controller, vicController, vicFactory, $q, $scope, $httpBackend, deferred;
var vicList = {
"results": [
{
"version": 5
},
{
"version": 1
}
]
};
var qs = '?sort=version&dir=DESC&page=1&limit=10&status=ACTIVE';
beforeEach(module('vicModule'));
beforeEach(inject(function(_$controller_, _vicFactory_, _$q_, _$rootScope_, _$httpBackend_, _$httpParamSerializer_) {
$controller = _$controller_;
vicFactory = {
getVICs: function () {
deferred = $q.defer();
return deferred.promise;
}
};
$q = _$q_;
$rootScope = _$rootScope_;
$scope = $rootScope.$new();
$httpBackend = _$httpBackend_;
$httpParamSerializer = _$httpParamSerializer_;
spyOn(vicFactory, 'getVICs').and.callThrough();
vicController = $controller('vicController', {vicFactory: vicFactory});
deferred.resolve(vicList);
}));
it('should be defined', function() {
expect(vicController).toBeDefined();
});
it('should call getVICs() on load', function() {
$httpBackend.expectGET(''/path/to/my/api?' + qs).respond(200, vicList);
$scope.$digest();
expect(vicFactory.getVICs).toHaveBeenCalledWith(qs);
expect($scope.vicList).toEqual(vicList);
});
});
controller
angular.module('vicModule')
.controller('vicController', vicController);
vicController.$inject = [
'vicFactory'
];
function vicController(vicFactory) {
var vm = this;
var qs = '?sort=version&dir=DESC&page=1&limit=10&status=ACTIVE';
vicFactory.getVICs(qs)
.then(function(response) {
console.log('vic controller success');
console.log(response);
vm.vicList = response;
}, function(response) {
console.log('vic controller error');
console.log(response);
});
}
Fixed
Got this working by injecting $q and $scope, setting vicFactory in beforeEach, updating spy, and then resolving the deferred with the expected data set after controller initialization. The controller test spec has been updated, hope this help someone.
In this Angular/Jasmine example I have a controller with a promise and a $timeout.
The test fails because a variable set in the $timeout is undefined. Other variables not set inside the $timeout don't have this problem.
Only this expect fails, the others work:
expect($scope.notes).toBe(notes);
I used $timeout.flush() to wait but it is ignored. Any ideas how to fix this?
PLUNK http://plnkr.co/edit/Jet3KRs7baTIzk8L30JQ
angular.module("mymodule", [])
.service('myHttp', function($http){
this.call = function($q) {
var defer = $q.defer();
$http({url:"gothere"})
.then(function (response) {
defer.resolve(response);
});
return defer.promise;
};
})
.controller('ctl', function($scope,$timeout,myHttp) {
$scope.read = function (id){
var data = {};
data.id = id;
myHttp.call({url:'/getRecord', data:data})
.then(function(response) {
$scope.id = response.id;
$scope.name = response.nm;
$scope.descrip = response.dsc;
$timeout(function(){
$scope.notes = response.nts;
},1000);
});
};
});
describe('Testing a Controller that uses a Promise', function () {
var $scope;
var $q;
var deferred;
beforeEach(module('mymodule'));
beforeEach(inject(function($controller, _$rootScope_, _$q_, $timeout, myHttp) {
$scope = _$rootScope_.$new();
$q = _$q_;
deferred = $q.defer();
spyOn(myHttp, 'call').and.returnValue(deferred.promise);
$controller('ctl', {
$scope: $scope,
$timeout: $timeout,
myHttp: myHttp
});
$scope.read(1)
$timeout.flush(2000);
}));
it('should resolve promise', function () {
var id = 1;
var name = "John";
var descrip = "This is the description";
var notes = "These are the notes";
var obj = {
id: id,
nm: name,
dsc: descrip,
nts: notes
};
deferred.resolve(obj);
$scope.$apply();
expect($scope.id).toBe(id);
expect($scope.name).toBe(name);
expect($scope.descrip).toBe(descrip);
expect($scope.notes).toBe(notes);
});
});
Give a timeout to read the variable :
it("should be proper after timeout", function(){
expect($scope.notes).toBe(notes);
}, 1000)
The answer is: use $timeout.flush() after $scope.apply();
$scope.$apply();
$timeout.flush();
I've mocked an httpBackend to test the following code.
self.Api.post('/endpoint/action/', actionData)
.then(function(resp){
result = _.get(resp, 'data.MessageList');
if(resp.status = 200 && result) {
for (var i = 0; i < result.length; i++) {
addResultMessage(result[i]);
}
}
});
I mocked my service doing the following:
beforeEach(function(){
angular.mock.module(function(ApiProvider) {
ApiProvider.setUrl('http://localhost:10010');
});
});
beforeEach(inject(function($rootScope, $controller, _$interval_, $q, $injector, $httpBackend){
$rootScope = $injector.get('$rootScope');
scope = $rootScope;
httpBackend = $httpBackend;
createController = function() {
return $controller('MyCtrl',{
$scope: scope,
$uibModalInstance: modalInstance,
$interval: _$interval_,
});
}
}));
Inside an it
httpBackend
.when('POST', 'http://localhost:10010/endpoint/action/')
.respond(200, mockedResponse);
I'm not getting any response when I do the POST to that endpoint. I don't get any error simply I don't get any resp callback
You should call flush() after the test, like so:
it('should read expired login data', inject(function(authSrvc) {
$httpBackend.expectGET('data/auth.json').respond(200, mockedResponse);
authSrvc.checkStatus().then(function(data){
expect(authSrvc.isValidSession()).toEqual(false);
});
$httpBackend.flush();
}));
I have a promise in a controller that I'm trying to test and I'm getting Error: Unexpected request: POST /v1/users.
I'm trying to spyOn the AuthService.changePassword which returns a promise and test whether it got called or not. Not sure why it's actually making the POST call...
controller
angular.module('example')
.controller('ChangePasswordCtrl', ['AuthService', '$state',
function(AuthService, $state) {
var vm = this;
vm.submitted = false;
vm.submit = function(valid) {
vm.submitted = true;
if (!valid) return false;
AuthService.changePassword(vm.email)
.then(function(res) {
$state.go('reset.confirmation');
}, function(err) {
vm.hasError = true;
});
};
}
]);
unit test
describe('ChangePasswordCtrl', function() {
var ctrl, scope, AuthService, $q, $state, deferred;
beforeEach(module('example'));
function _inject() {
inject(function($controller, $rootScope, _AuthService_, _$state_, _$q_) {
scope = $rootScope.$new();
$state = _$state_;
$q = _$q_;
AuthService = _AuthService_;
ctrl = $controller('ChangePasswordCtrl', {
$scope: scope
});
});
}
describe('#submit', function() {
beforeEach(function() {
_inject();
deferred = $q.defer();
spyOn(AuthService, 'changePassword').and.returnValue(deferred.promise);
spyOn($state, 'go');
});
describe('when email address is valid', function() {
it('should call the changePassword method on the AuthService', function() {
ctrl.submit(true);
scope.$digest();
expect(ctrl.submitted).toBe(true);
expect(AuthService.changePassword).toHaveBeenCalled();
});
});
});
});
Your spec code works for me (the real implementation of AuthService.changePassword doesn't get called): http://jsfiddle.net/7W2XB/7/
angular.module('example', [])
.factory('AuthService', function() {
return {
changePassword: function() {
throw new Error('Should not be called');
}
};
})
.controller('ChangePasswordCtrl', ['AuthService',
function(AuthService) {
var vm = this;
vm.submitted = false;
vm.submit = function(valid) {
vm.submitted = true;
if (!valid) return false;
AuthService.changePassword(vm.email)
.then(function(res) {
$state.go('reset.confirmation');
}, function(err) {
vm.hasError = true;
});
};
}
]);
describe('ChangePasswordCtrl', function() {
var ctrl, scope, AuthService, $q, deferred;
function _inject() {
module('ui.router');
module('example');
inject(function($controller, $rootScope, _AuthService_, _$state_, _$q_) {
scope = $rootScope.$new();
$state = _$state_;
$q = _$q_;
AuthService = _AuthService_;
ctrl = $controller('ChangePasswordCtrl', {
$scope: scope
});
});
}
describe('#submit', function() {
beforeEach(function() {
_inject();
deferred = $q.defer();
spyOn(AuthService, 'changePassword').and.returnValue(deferred.promise);
});
describe('when email address is valid', function() {
it('should call the changePassword method on the AuthService', function() {
ctrl.submit(true);
scope.$digest();
expect(ctrl.submitted).toBe(true);
expect(AuthService.changePassword).toHaveBeenCalled();
});
});
});
});
Some questions that might help make the JSFiddle more realistic to your situation: What versions of angular and Jasmine are you using? - How are you defining the AuthService (presumably using angular.factory)?
My service looks like this:
app.service('foo',function($q){
this.fn3 = function(){
var deferred = $q.defer();
return deferred.promise;
}
});
My spec document is as below:
describe('Testing: MainCtrl', function() {
var $scope = null;
var ctrl = null;
var mockfoo;
var deferred;
var $q;
var data = {name: 'test'};
var createController;
beforeEach(module('plunker'));
beforeEach(inject(function($rootScope, $controller,_$q_) {
$scope = $rootScope.$new();
$q = _$q_;
mockfoo = {
fn3: function(){ }
};
deferred = $q.defer();
spyOn(mockfoo,'fn3').and.returnValue(deferred.promise);
createController = function() {
return $controller('MainCtrl', {
$scope: $scope,
foo: mockfoo
});
};
}));
it('Should call fn3()', function(){
ctrl = createController();
deferred.resolve(data);
$scope.$digest();
expect(mockfoo.fn3).toHaveBeenCalled();
expect($scope.temp).toBe(data);
});
});
I am not quiet sure why this spec is failing. I am resolving the promise and then calling $digest which means that $scope.temp should get a value.
I have a plnkr for this here:
http://plnkr.co/edit/uzWqTT
Any suggestions anybody ?
You have to catch the resolved value by accessing the value in the then function:
var resolvedValue;
$scope.temp.then(function(value) { resolvedValue = value; });
expect(resolvedValue).toEqual(data);
See plunker.