Karma - Unit Testing Angular Controller with Mocked API - angularjs

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.

Related

angularjs unit testing (cannot find propery of 'resolve' undefined angularjs testing)

I am trying to do unit test of my angular app with karma. I am getting some error. Am i missing something? A
This my controller
(function () {
"use strict"
angular
.module("myApp")
.controller("userCtrl",['$scope', '$state', 'userService', 'appSettings','md5','currentUser','$rootScope',
function ($scope, $state, userService, appSettings,md5,currentUser, $rootScope) {
$scope.login = function() {
$scope.loading = true;
if($scope.password != null){
var user ={
username:$scope.username,
password:md5.createHash($scope.password)
}
var getData = userService.login(user);
getData.then(function (response) {
console.log(response);
$scope.loading = false;
currentUser.setProfile(user.username, response.data.sessionId);
$state.go('videos');
}, function (response) {
console.log(response.data);
});
}else{
$scope.msg = "Password field is empty!"
}
}
}])
}());
This is my test codes
'use strict';
describe('userCtrl', function() {
beforeEach(module('myApp'));
var scope, userCtrl, apiService,q, deferred, currentUser;
describe('$scope.login', function(){
beforeEach(function(){
apiService = {
login: function () {
deferred = q.defer();
return deferred.promise;
};
};
});
beforeEach(inject(function($controller, $rootScope, $q, _currentUser_){
var user ={name:'ali',password:'password'};
scope = $rootScope.$new();
q = $q;
// The injector unwraps the underscores (_) from around the parameter names when matching
userCtrl = $controller('userCtrl', {
$scope:scope,
userService:apiService
});
//userService = _userService_;
currentUser = _currentUser_;
}));
it('should call user service login', function() {
spyOn(apiService, 'login').and.callThrough();
scope.login();
deferred.resolve(user);
expect(apiService.login).toHaveBeenCalled();
});
it('checks the password field', function() {
scope.login();
expect(scope.msg).toEqual('Password field is empty!');
});
});
});
And i am getting this error
enter image description here
If you have to test controller then use to spyon for service method and in case of service then use HttpBackend
describe('Testing a Controller that uses a Promise', function() {
var $scope;
var $q;
var deferred;
beforeEach(module('search'));
beforeEach(inject(function($controller, _$rootScope_, _$q_, searchService) {
$q = _$q_;
$scope = _$rootScope_.$new();
// We use the $q service to create a mock instance of defer
deferred = _$q_.defer();
// Use a Jasmine Spy to return the deferred promise
spyOn(searchService, 'search').and.returnValue(deferred.promise);
// Init the controller, passing our spy service instance
$controller('SearchController', {
$scope: $scope,
searchService: searchService
});
}));
it('should resolve promise', function() {
// Setup the data we wish to return for the .then function in the controller
deferred.resolve([{
id: 1
}, {
id: 2
}]);
// We have to call apply for this to work
$scope.$apply();
// Since we called apply, not we can perform our assertions
expect($scope.results).not.toBe(undefined);
expect($scope.error).toBe(undefined);
});
});
This for same using spyon for service method then use $appy method to make it work.

Angular karma testing controller

I'm trying to figure out these karma tests to test my Angular app. I can figure out simple things. But I am having problems with one that connects to my database.
I am wanting to test the $scope.getMultipleOptions function in the controller below:
$scope.options = [];
$scope.getMultipleOptions("form_field_1", $scope.options);
$scope.getMultipleOptions = function (key, opts) {
var id = key.replace("form_field_", "");
var promise = DynamicFormFactory.GetData('/DynamicForm/GetMultipleOptions?form_field_id=" + id);
promise.then(
function (success) {
angular.forEach(success, function (o) {
opts.push(o);
});
},
function (error) {
// Error
}
);
}
This is what my factory/service looks like:
dynamicFormApp.factory('DynamicFormFactory', ['$http', function ($http) {
return {
GetData: function (url) {
return $http.get(url)
.then(function (response) {
return response.data;
}, function (error) {
return error;
});
}
}]);
And this is what I've done to set up my karma test
describe('DynamicFormController', function () {
beforeEach(module('dynamicFormApp'));
var $controller;
var $rootScope;
beforeEach(inject(function (_$controller_, _$rootScope_) {
// The injector unwraps the underscores (_) from around the parameter names when matching
$controller = _$controller_;
$rootScope = _$rootScope_;
}));
describe('$scope.getMultipleOptions', function () {
var $scope, controller;
beforeEach(function () {
$scope = $rootScope.$new();
controller = $controller('DynamicFormController', { $scope: $scope });
$scope.fields = [];
});
it('$scope.getMultipleOptions', function () {
var key = "form_field_15";
var expectedResult = [{ desc: "DESCRIPTION", id: "ID" }];
$scope.getMultipleOptions(key, $scope.fields);
expect($scope.fields).toEqual(expectedResult);
});
});
});
The test always fails as $scope.fields is always empty. How do I do this?

Testing John Papa's controller calls to dataservice

I am learning BDD with Jasmine and Karma.
I'm following John Papa's style guide and I have some difficulties testing my code.
I want to test this kind of controller function (from John Papa's guide):
function getAvengers() {
return dataservice.getAvengers()
.then(function(data) {
vm.avengers = data;
return vm.avengers;
});
}
With this factory:
function dataservice($http) {
return {
getAvengers: getAvengers
};
function getAvengers() {
return $http.get('/api/maa')
.then(getAvengersComplete)
.catch(getAvengersFailed);
function getAvengersComplete(response) {
return response.data.results;
}
function getAvengersFailed(error) {
// Fail
}
}
}
I've made these test:
describe("avengers controller", function() {
var controller, avengers;
beforeEach(function() {
module('app');
});
beforeEach(inject(function(_$rootScope_, _$controller_, _$q_, _dataservice_) {
dataserviceMock = _dataservice_;
controller = _$controller_;
vm = controller("AvengerController", { dataservice: dataserviceMock });
$q = _$q_;
rootScope = _$rootScope_;
avengers = [{"id": 1}, {"id": 2}];
}));
it('should be created successfully', function () {
expect(vm).toBeDefined();
});
it('should have called `dataservice.getAvengers`', function() {
spyOn(dataserviceMock, 'getAvengers').and.callFake(function(){
var deferred = $q.defer();
return deferred.promise;
});
vm.getAvengers();
expect(dataserviceMock.getAvengers).toHaveBeenCalledTimes(1);
});
// What I'm trying to test
// Test 1
it('should have Avengers', function() {
spyOn(dataserviceMock, 'getAvengers').and.callFake(function(){
var deferred = $q.defer();
deferred.resolve([{"id": 1}, {"id": 2}]);
return deferred.promise;
);
vm.getAvengers();
expect(vm.avengers.length).toEqual(avengers.length);
});
// Test 2
it('should have Avengers', function() {
spyOn(dataserviceMock, 'getAvengers').and.returnValue(avengers);
vm.getAvengers();
expect(vm.avengers.length).toEqual(avengers.length);
});
});
I tried two different ways but it didn't work and I can't figure out how to test that avengerController.getAvengers() defines vm.avengers equal to the returned data avengers from the factory call.
I found a solution:
it("should have avengers", function() {
deferred = $q.defer();
spyOn(dataserviceMock, 'getAvengers').and.returnValue(deferred.promise);
deferred.resolve(customers);
vm.getAvengers();
expect(dataserviceMock.getAvengers).toHaveBeenCalled();
scope.$digest();
expect(vm.avengers).toBeDefined();
expect(vm.avengers).toEqual(avengers);
});
I had to return a promise and use the scope.$digest() to simulate the scope life cycle as mentionned here.

can't get promise result from angular service

I'm trying to mock a service I'm using and should return a promise, the mock service is being called but I can't get the result to my test.
service function to be tested:
function getDevices() {
console.log('getDevices');
return servicesUtils.doGetByDefaultTimeInterval('devices')
.then(getDevicesComplete);
function getDevicesComplete(data) {
console.log('getDevicesComplete');
var devices = data.data.result;
return devices;
}
}
My test is:
describe('devicesService tests', function () {
var devicesService;
var servicesUtils, $q, $rootScope;
beforeEach(function () {
servicesUtils = {};
module('app.core', function ($provide) {
servicesUtils = specHelper.mockServiceUtils($provide, $q, $rootScope);
});
inject(function (_devicesService_, _$q_, _$rootScope_) {
devicesService = _devicesService_;
$q = _$q_;
$rootScope = _$rootScope_.$new();
});
});
it('getting device list', function () {
console.log('getting device list');
devicesService.getDevices().then(function (result) {
console.log(result);
expect(result).toBeDefined();
});
});
});
Mock file:
function mockServiceUtils($provide, $q) {
var servicesUtils = {};
servicesUtils.doGetByDefaultTimeInterval = jasmine.createSpy().and.callFake(function() {
var deferred = $q.defer();
deferred.resolve('Remote call result');
$rootScope.$digest();
return deferred.promise;
});
$provide.value('servicesUtils', servicesUtils);
return servicesUtils;
}
Your code is way too complex.
Let's assume that you want to test a service devicesService that uses another service servicesUtils, having a method that returns a promise.
Let's assume devicesService's responsibility is to call servicesUtils and transform its result.
Here's how I would do it:
describe('devicesService', function() {
var devicesService, servicesUtils;
beforeEach(module('app.core'));
beforeEach(inject(function(_devicesService_, _servicesUtils_) {
devicesService = _devicesService_;
servicesUtils = _servicesUtils_;
}));
it('should get devices', inject(function($q, $rootScope) {
spyOn(servicesUtils, 'doGetByDefaultTimeInterval').and.returnValue($q.when('Remote call result'));
var actualResult;
devicesService.getDevices().then(function(result) {
actualResult = result;
});
$rootScope.$apply();
expect(actualResult).toEqual('The transformed Remote call result');
}));
});

Spyon angular service method returns Unexpected POST

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)?

Resources