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();
}));
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.
This is the function in the controller:
var vm = this;
vm.getData = getData;
function getData(val) {
return $http.get('/get-data', {
params: {
query: val
}
}).then(function(response) {
return response.data;
});
}
and this is my (stripped down) test file:
describe('Controller: MyCtrl', function() {
'use strict';
var MyCtrl;
var rootScope;
var scope;
var httpMock;
beforeEach(function() {
module('MyModule');
inject(function($controller, $rootScope, $httpBackend) {
rootScope = $rootScope;
scope = $rootScope.$new();
httpMock = $httpBackend;
MyCtrl = $controller('MyCtrl as vm', {
$rootScope: rootScope,
$scope: scope,
$http: httpMock,
});
});
});
describe('vm.getData()', function() {
it('returns the required data', function() {
httpMock.when('GET', '/get-data?query=test-val').respond(200, {data: 'test-data'});
httpMock.flush();
expect(scope.vm.getData('test-val')).toEqual('test-data');
});
});
});
I would like to test that the result if calling getData() return the correct data.
Currently I'm getting the error $http.get is not a function. Setting a breakpoint in my function shows that http is stubbed with $httpBackend though.
I think there is something fundamental I'm not grasping - any pointers would be greatly appreciated.
You shouldn't have to create the controller with:
$http: $httpBackend
to mock the backend. $httpBackend will mock the request itself already.
Further the test and assertion is done in the wrong order:
httpMock.when('GET', '/get-data?query=test-val').respond(200, {data: 'test-data'});
MyCtrl.getData('test-val').then(function(_result_){ //perform the request
result = _result_; //save the result of the promise
});
httpMock.flush(); //execute the request
expect(result).toBe('test-data'); //assert that the result is as expected
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.
My controller has:
switchUserAccount: function() {
$scope.model.currentMode = 'user';
console.log(ipCookie('currentPatientId'));
$q.all([facilityCache.getFacility(), facilityGroupCache.getGroupList(), languageCache.getLanguageList(), genderCache.getGenderList(), raceCache.getRaceList(), dosingCache.getDosingOptions()])
.then(function(){
console.log('back from then');
cache.set('ui', 'adminPage', '');
cache.set('ui', 'schedulePage', 'patients');
if(ipCookie('currentPatientId')) {
$location.path('/patient/view/' + ipCookie('currentPatientId'));
} else {
$location.path('/patients');
}
});
},
and my test is
describe('MainHeaderController', function() {
var scope, $rootScope, $locationMock, $httpBackend;
beforeEach(function() {
module('mapApp');
return inject(function($injector) {
var $controller, $q, ipCookieMock;
$rootScope = $injector.get('$rootScope');
$controller = $injector.get('$controller');
$httpBackend = $injector.get('$httpBackend');
$q = $injector.get('$q');
$locationMock = jasmine.createSpyObj('$location', ['path'])
ipCookieMock = function() {
return 123;
}
scope = $rootScope.$new()
$controller('MainHeaderController', {
$scope: scope,
$location: $locationMock,
$q: $q,
ipCookie: ipCookieMock
});
$httpBackend.whenGET('/rpc/session').respond(200);
$httpBackend.whenPOST('/rpc').respond(200);
return scope.$digest();
});
});
it('should redirect to a patient view if a cookie is set', function($rootScope) {
scope.switchUserAccount();
// $rootScope.$apply();
expect($locationMock.path).toHaveBeenCalledWith('/patient/view/123');
});
});
So what I expect to happen is for $location.path to be called with /patient/view/123. Instead, what I get is
Expected spy $location.path to have been called with [ '/patient/view/123' ] but actual calls were [ ].
If I uncomment out the $rootScope.$apply(), I get
TypeError: 'undefined' is not a function (evaluating '$rootScope.$apply()')
So how can I trigged the $q.all in my controller so that the test can pass properly?
Thanks!
You're hiding the $rootScope variable of your test suite by declaring it as an argument of your test function. That's why it's undefined: jasmine calls the test functions withput any argument.
Replace
it('should redirect to a patient view if a cookie is set', function($rootScope) {
by
it('should redirect to a patient view if a cookie is set', function() {
My controller is:
angularMoonApp.controller('SourceController', ['$scope', '$rootScope', '$routeParams', 'fileService', function ($scope, $rootScope, $routeParams, fileService) {
$scope.init = function() {
$rootScope.currentItem = 'source';
fileService.getContents($routeParams.path).then(function(response) {
$scope.contents = response.data;
$scope.fileContents = null;
if(_.isArray($scope.contents)) {
// We have a listing of files
$scope.breadcrumbPath = response.data[0].path.split('/');
} else {
// We have one file
$scope.breadcrumbPath = response.data.path.split('/');
$scope.breadcrumbPath.push('');
$scope.fileContents = atob(response.data.content);
fileService.getCommits(response.data.path).then(function(response) {
$scope.commits = response.data;
});
}
});
}
$scope.init();
}]);
My test is pretty simple:
(function() {
describe('SourceController', function() {
var $scope, $rootScope, $httpBackend, createController;
beforeEach(module('angularMoon'));
beforeEach(inject(function($injector) {
$httpBackend = $injector.get('$httpBackend');
$rootScope = $injector.get('$rootScope');
$scope = $rootScope.$new();
var $controller = $injector.get('$controller');
createController = function() {
return $controller('SourceController', {
'$scope': $scope
});
};
}));
it("should set the current menu item to 'source'", function() {
createController();
$scope.init();
expect($rootScope.currentItem).toBe('source');
});
it("should get the contents of the root folder", function() {
createController();
$scope.init();
// NOT SURE WHAT TO DO HERE!
});
});
})();
I want to test that the fileService had it's getContents function called and mock a response so that I can test the two scenarios (if is array and if isn't`)
I would recommend using Jasmine spies for this.
Here is an example that might help. I usually put the spyOn call in the beforeEach.
var mockedResponse = {};
spyOn(fileService, "getContents").andReturn(mockedResponse);
In the 'it' part:
expect(fileService.getContents).toHaveBeenCalled();
To get the response, just call the method in your controller that calls the fileService method. You may need to manually run a digest cycle too. Snippet from one of my tests:
var testOrgs = [];
beforeEach(inject(function(coresvc) {
deferred.resolve(testOrgs);
spyOn(coresvc, 'getOrganizations').andReturn(deferred.promise);
scope.getAllOrganizations();
scope.$digest();
}));
it("getOrganizations() test the spy call", inject(function(coresvc) {
expect(coresvc.getOrganizations).toHaveBeenCalled();
}));
it("$scope.organizations should be populated", function() {
expect(scope.allOrganizations).toEqual(testOrgs);
expect(scope.allOrganizations.length).toEqual(0);
});
deferred in this case is a promise created with $q.defer();
You can create a spy and verify only that fileService.getContents is called, or either verify extra calls (like promise resolution) by making the spy call through. Probably you should also interact with httpBackend since you may need to flush the http service (even though you use the mock service).
(function() {
describe('SourceController', function() {
var $scope, $rootScope, $httpBackend, createController, fileService;
beforeEach(module('angularMoon'));
beforeEach(inject(function($injector) {
$httpBackend = $injector.get('$httpBackend');
$rootScope = $injector.get('$rootScope');
$scope = $rootScope.$new();
// See here
fileService = $injector.get('fileService');
spyOn(fileService, 'getContents').andCallThrough();
var $controller = $injector.get('$controller');
createController = function() {
return $controller('SourceController', {
'$scope': $scope
'fileService': fileService
});
};
}));
it("should get the contents of the root folder", function() {
createController();
$scope.init();
expect(fileService.getContents).toHaveBeenCalled();
});
});
})();
You can also add expectations to what happens inside the callback but you should issue a httpBackend.flush() before.