For testing with Angular.js, Mocha, and Jasmine I have this test for DeviceVal value file:
describe("load balancer device: value", function() {
var DeviceVal;
DeviceVal = null;
beforeEach(function() {
return module("main.loadbalancer");
});
beforeEach(inject(function(_DeviceVal_) {
return DeviceVal = _DeviceVal_;
}));
return it("should default the id to 'device not loaded'", function() {
return expect(DeviceVal).toEqual({
readonly: false,
lb: "device not loaded"
});
});
});
For one of my other tests, I have this which uses the DeviceVal value file:
describe("load balancer controller: readonly", function() {
$scope = DeviceVal = LoadBalancerSvc = SearchSvc = $state = $sce = null;
beforeEach(function() {
module('main');
});
beforeEach(inject(function($controller, $rootScope, _$state_, _LoadBalancerSvc_, _DeviceVal_) {
$scope = $rootScope.$new();
$state = _$state_;
LoadBalancerSvc = _LoadBalancerSvc_;
DeviceVal = _DeviceVal_;
$controller('HeaderCtrl', {$scope: $scope});
//to resolve all the promises we have on the mocked service
$scope.$digest();
lb = { ha_status: "secondary", id: "34584"};
this.lb = lb;
spyOn(LoadBalancerSvc, "searchDevice").and.returnValue(
{get: function() {
return { then: function(fn) { fn(lb)} }}});
spyOn($state, "go");
}));
it('scope data memembers to have their stuff', function() {
$scope.searchButton.submit();
expect(lb).toEqual(this.lb);
expect(lb['ha_status']).toEqual("secondary");
expect(DeviceVal.readonly).toBe(true);
expect($state.go).toHaveBeenCalledWith("main.loadbalancer.vips", {id: this.lb.id});
});
});
The issue I am having is I want to manually set the values in the second test of DeviceVal to custom values. But instead, the test expects the values to be the values in the first DeviceVal test file. It complains of:
Expected { readonly : true, lb : { ha_status : 'secondary', id : '34584' } } to equal { readonly : false, lb : 'device not loaded' }.
How can I make the second test where I can manually set the values DeviceVal.lb = { ha_status: "secondary", id: "34584"}; and have expect(lb['ha_status']).toEqual("secondary"); and expect(DeviceVal.readonly).toBe(true);?
UPDATE:
Here it is with mock DeviceVal, but I still get Error: Expected { readonly : true, lb : { ha_status : 'secondary', id : '34584' } } to equal { readonly : fals
e, lb : 'device not loaded' }.:
describe("load balancer controller: readonly", function() {
$scope = DeviceVal = LoadBalancerSvc = SearchSvc = $state = $sce = null;
beforeEach(function() {
module('main');
});
beforeEach(inject(function($controller, $rootScope, _$state_, _LoadBalancerSvc_, _DeviceVal_) {
$scope = $rootScope.$new();
$state = _$state_;
LoadBalancerSvc = _LoadBalancerSvc_;
DeviceVal = _DeviceVal_;
$controller('HeaderCtrl', {$scope: $scope});
//to resolve all the promises we have on the mocked service
$scope.$digest();
//MY ATTEMPT AT MOCKING THIS DATA
DeviceVal.lb = { ha_status: "secondary", id: "34584"};
DeviceVal.readonly = true;
spyOn(LoadBalancerSvc, "searchDevice").and.returnValue(
{get: function() {
return { then: function(fn) { fn(DeviceVal.lb)} }}});
spyOn($state, "go");
}));
it('scope data memembers to have their stuff', function() {
$scope.searchButton.submit();
expect(DeviceVal.lb).toEqual({ha_status: "secondary", id: "34584"});
expect(DeviceVal.lb['ha_status']).toEqual("secondary");
expect(DeviceVal.readonly).toBe(true);
expect($state.go).toHaveBeenCalledWith("main.loadbalancer.vips", {id: DeviceVal.lb.id});
});
});
Fixed it. From http://angular-tips.com/blog/2014/06/introduction-to-unit-test-controllers/, I created the mock data like this:
describe("load balancer controller: readonly", function() {
$scope = DeviceVal = LoadBalancerSvc = SearchSvc = $state = $sce = null;
beforeEach(function() {
eviceVal = {}
module('main', function($provide) {
$provide.value('DeviceVal', eviceVal);
});
inject(function() {
eviceVal.lb = { ha_status: "secondary", id: "34584"};
eviceVal.readonly = true;
});
});
Related
In my controller, I am checking if page is iframed or not by this condition:
var vm = this;
if ($window.self !== $window.top) {
//We are iframed
vm.weAreIFramed = true
}
else {
vm.weAreIFramed = false;
}
I want to write a unit test for it. I wrote something like this but the test is always failing:
var homeController, $httpBackend, $controller, $location, $rootScope;
var topObj = {};
var selfObj = {};
var windowObj = {
location: { href: '' },
top: topObj,
self: topObj
};
beforeEach(module("app"), function ($provide) {
$provide.value('$window', windowObj);
});
beforeEach(inject(function (_$httpBackend_, _$controller_, _$location_, _$rootScope_) {
$httpBackend = _$httpBackend_;
$controller = _$controller_;
$location = _$location_;
$rootScope = _$rootScope_;
homeController = $controller("homeController");
}));
afterEach(function () {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it("vm.weAreIFramed should be false if we are not iFramed", function () {
console.log(angular.mock.dump(windowObj));
expect(homeController.weAreIFramed).toEqual(false);
});
It fails with below error:
How to write the correct test to check if the page is iframed or not ?
The test fails because "" === "". In real window, top and self are objects.
It should be
var topObj = {};
var selfObj = {};
var windowObj = {
location: { href: '' },
top: topObj,
self: selfObj
};
And for the other condition, only topObj can be specified for both.
I'm new to AngularJS and unit testing,
I'm testing a list that gets changing by selected category.
The test is passing but only if I use the httpBackend.expectGET() that expects the XMLHttpRequest from the "getSomethingElse" method.
I also tried to use the scope.$digest() but I got the same results...
The Controller:
app.controller('mainCtrl', ['$scope', 'myService', function($scope,
myService) {
$scope.category = null;
myService.getSomethingElse().then(function(res) {
$scope.somethingElse = res.data;
});
$scope.$watch('category', function() {
if ($scope.category !== null) {
myService.getListByCat($scope.category.name).then(function(res) {
$scope.list = res.data;
});
}
else {
myService.getLongList().then(function(res) {
$scope.list = res.data;
});
}
});
}]);
The Service:
app.service('myService', ['$http', function($http) {
this.getListByCat = function(category) {
return $http.get('getting-list?cat=' + category);
};
this.getLongList = function() {
return $http.get('getting-long-list');
};
this.getSomethingElse = function() {
return $http.get('getting-something-else');
};
}]);
The Test
describe('Testing mainCtrl', function() {
var scope, ctrl;
var myServiceMock = {
getSomethingElse: jasmine.createSpy().and.returnValue(1),
getListByCat: jasmine.createSpy().and.returnValue(2)
};
beforeEach(function() {
module('app');
inject(function($rootScope, $controller) {
scope = $rootScope.$new();
ctrl = $controller('mainCtrl', {
$scope: scope,
myService: myServiceMock
});
});
});
it('should update the list by selected category', function() {
expect(scope.category).toBeNull();
expect(scope.list).toBeUndefined();
scope.category = {
id: 1,
name: 'Jobs'
};
scope.$apply();
expect(myServiceMock.getSomethingElse).toHaveBeenCalled();
expect(myServiceMock.getListByCat).toHaveBeenCalled();
});
});
The test is passing but only if I use the httpBackend.expectGET() that expects the XMLHttpRequest from the "getSomethingElse" method.
This is because your myServiceMock is not replacing the original myService. You have various ways to test this - one of them is given below. Here we are replacing myService with the service mock:-
beforeEach(function() {
module('app');
module(function($provide){
$provide.factory('myServiceMock',
function(){
return myServiceMock;
);
});
inject(function($rootScope, $controller) {
scope = $rootScope.$new();
ctrl = $controller('mainCtrl', {
$scope: scope,
myService: myServiceMock
});
});
});
You can add your watcher like this.
$scope.categoryWatcher = categoryWatcher;
$scope.$watch('category', categoryWatcher);
function categoryWatcher() {
if ($scope.category !== null) {
myService.getListByCat($scope.category.name).then(function(res) {
$scope.list = res.data;
});
}
else {
myService.getLongList().then(function(res) {
$scope.list = res.data;
});
}
}
and in Unit testing just create new it construct for that handler
it('should test categoryWatcher for null value', function(){
$scope.category = null;
$scope.categoryWatcher();
// your expectations
});
it('should test categoryWatcher for "desiredValue" value', function(){
$scope.category = "desiredValue";
$scope.categoryWatcher();
// your expectations
});
that way, if&else clauses will be taken in the test.
I am trying to test my controller but I get undefined when running karma start
This is my controller Code:
(function() {
'use strict';
angular
.module('tariffOptionsExtras')
.controller('tariffOptionsExtrasBookCtrl', tariffOptionsExtrasBookCtrl);
tariffOptionsExtrasBookCtrl.$inject = ['tariffOptionsExtrasSrv', '$location', 'cache', 'authSrv', '$stateParams',
'tariffOptionsExtrasBookSrv', 'minimumDuration', '$rootScope', 'userTypes', 'messageCtrl', '$state'];
function tariffOptionsExtrasBookCtrl(tariffOptionsExtrasSrv, $location, cache, authSrv, stateParams, tariffOptionsExtrasBookSrv, minimumDuration, $rootScope, userTypes, messageCtrl, $state) {
var vm = this;
var cacheName = 'tariffOptionsExtras';
var cacheCollection = cache.getCollection(cacheName);
vm.date = new Date();
vm.minimumDuration = minimumDuration;
//if statement commented out. prepaid users should be able to enter the flow.
Date(vm.bookOption.startDate)
}
if (!vm.bookOption) {
$state.go(pagesConfig.tariffOptionsExtras.name);
} else {
vm.infoLink = vm.bookOption.infoUrl;
}
/**
* Runs booking tarif extra post and in case of success the view is changed
*/
vm.book = function() {
//If bookoption not present, redirect to chooser
tariffOptionsExtrasBookSrv.bookTariffExtra(vm.bookOption).then(function(response) {
$rootScope.$broadcast('transactionFinished');
var item = response['salesOrderVBO'][0]['orderItems']['orderItem'][0];
if (item.product.action == 'BOOKED') {
vm.success = true;
}
}, function(reason) {
vm.errorMessage = reason.data.faultMessage;
});
};
vm.success = false;
vm.subscription = authSrv.getUserContract();
vm.msisdn = vm.subscription.subscription.msisdn;
}
})();
Unit Test with jasmine
describe('tariffOptionsExtras module', function() {
describe('tariffOptionsExtrasBook Controller', function() {
var tariffOptionsExtrasBookCtrl, tariffOptionsExtrasSrv, tariffOptionsExtrasBookSrv, authSrv;
// Step 1: Import the module this controller belongs to, and its dependencies
beforeEach(function() {
module('app.common');
module('tariffOptionsExtras');
});
// Step 2: Mock any service that initially used when the controller instantiate
// beforeEach(module(function($provide) {
// $provide.factory('tariffOptionsExtrasBookSrv', function() {
// var getSync;
// getBookedExtras = function() {
// return {
// then:function(){}
// };
// };
// getTariffBookableOptions = function() {
// return {
// then:function(){}
// };
// };
// return {
// getBookedExtras: getBookedExtras,
// getTariffBookableOptions:getTariffBookableOptions
// };
// });
// }));
beforeEach(module(function($provide) {
$provide.factory('authSrv', function() {
getUserContract = function() {
return {
subscription:{
msisdn:'491741660390',
mboName:'27434975'
},
contract:{
mboName:'27434975',
ban:'106491816',
}
};
};
return {
getUserContract: getUserContract,
};
});
}));
// Step 3: Inject $controller with its dependencies
beforeEach(function() {
// 1. Import the module
module('app');
// 2. Inject $controller
inject(function($controller, $rootScope, _authSrv_, _tariffOptionsExtrasSrv_, _tariffOptionsExtrasBookSrv_) {
authSrv = _authSrv_;
tariffOptionsExtrasSrv = _tariffOptionsExtrasSrv_;
tariffOptionsExtrasBookSrv = _tariffOptionsExtrasBookSrv_;
// 3. Use $controller to instantiate the controller
tariffOptionsExtrasBookCtrl = $controller('tariffOptionsExtrasBookCtrl', {
'authSrv': authSrv,
'tariffOptionsExtrasSrv': tariffOptionsExtrasSrv,
'tariffOptionsExtrasBookSrv': tariffOptionsExtrasBookSrv
});
});
});
// Step 4: Test the controller
it('Should return sum of 1+1', function() {
expect(1+1).toBe(2);
});
});
});
When running karma Start I get this screen:
enter image description here
Also when I comment this block of code it works:
tariffOptionsExtrasBookCtrl = $controller('tariffOptionsExtrasBookCtrl', {
'authSrv': authSrv,
'tariffOptionsExtrasSrv': tariffOptionsExtrasSrv,
'tariffOptionsExtrasBookSrv': tariffOptionsExtrasBookSrv
});
first you have to inject the $controller param must be enclosed with underscores
then assign it to the $controller variable as this example
beforeEach(inject(function ($rootScope, _$controller_) {
scope = $rootScope.$new();
$controller = _$controller_;
$controller('nameController', {$scope: scope});
}));
I have written a unit test like below
describe('modals', function() {
beforeEach(module('DetailsApp'));
var controller, rootScope, templateCache, compile, http, httpBackend;
var uibModalInstance = {
dismiss: function(message) {
},
close: function(message) {
}
};
var plugins = {
get: function(plugin) {
if(plugin == 'Workorder'){
return {
'workorder_id': 'workorder_id'
};
} else if(plugin == 'CompanyInfo'){
return {
'company_name': 'company_name',
'company_id': 'company_id'
};
}
}
};
beforeEach(module(function($provide) {
$provide.value('$uibModalInstance', uibModalInstance);
$provide.value('plugins', plugins);
}));
beforeEach(inject(function($controller, $templateCache, $compile, $rootScope, $http, $httpBackend) {
controller = $controller;
templateCache = $templateCache;
compile = $compile;
rootScope = $rootScope;
http = $http;
httpBackend = $httpBackend;
}));
describe('When modal functions are called', function() {
it('they should be called correctly', function() {
var $scope = {};
var companyRatingHistory = controller('companyRatingHistory', { $scope: $scope });
spyOn(uibModalInstance, 'dismiss');
spyOn(uibModalInstance, 'close');
$scope.cancel();
expect(uibModalInstance.dismiss).toHaveBeenCalledWith('cancel');
$scope.close('close');
expect(uibModalInstance.close).toHaveBeenCalledWith('close');
});
}); });
and found that my code coverage shows an E in plugins else block like below
else if(plugin == 'CompanyInfo'){
return {
'company_name': 'company_name',
'company_id': 'company_id'
};
}
What i have missed in my test. Advance thanks and get any suggestions from anybody who helps me.
I have a custom directive that uses an external template and is passed data from a service. I decided to ensure that the promise was resolved before modifying the data, which was fine in the actual code but broke my unit tests, which is annoying. I have tried a number of variations but am now stuck. I am using 'ng-html2js' preprocessor.
Here is the unit test
describe('ccAccordion', function () {
var elm, scope, deferred, promise, things;
beforeEach(module('ccAccordion'));
// load the templates
beforeEach(module('components/accordion/accordion.html'));
beforeEach(inject(function ($rootScope, $compile, $q) {
elm = angular.element(
'<cc-accordion items="genres"></cc-accordion>'
);
scope = $rootScope;
things = [{
title: 'Scifi',
description: 'Scifi description'
}, {
title: 'Comedy',
description: 'Comedy description'
}];
deferred = $q.defer();
promise = deferred.promise;
promise.then(function (things) {
scope.items = things;
});
// Simulate resolving of promise
deferred.resolve(things);
// Propagate promise resolution to 'then' functions using $apply().
scope.$apply();
// compile the template?
$compile(elm)(scope);
scope.$digest();
}));
it('should create clickable titles', function () {
var titles = elm.find('.cc-accord h2');
expect(titles.length).toBe(2);
expect(titles.eq(0).text().trim()).toBe('Scifi');
expect(titles.eq(1).text().trim()).toBe('Comedy');
});
I have left out the custom addMatchers and the rest of the tests. The error I get is
TypeError: 'undefined' is not an object (evaluating 'scope.items.$promise')
Here is the directive
var ccAccordion = angular.module("ccAccordion", []);
ccAccordion.directive("ccAccordion", function () {
return {
restrict: "AE",
templateUrl: "components/accordion/accordion.html",
scope: {
items: "="
},
link: function (scope) {
scope.items.$promise.then(function (items) {
angular.forEach(scope.items, function (item) {
item.selected = false;
});
items[0].selected = true;
});
scope.select = function (desiredItem) {
(desiredItem.selected === true) ? desiredItem.selected = false : desiredItem.selected = true;
angular.forEach(scope.items, function (item) {
if (item !== desiredItem) {
item.selected = false;
}
});
};
}
};
});
This is where the directive is used in main.html
<cc-accordion items="genres"></cc-accordion>
In the main controller the genres service is passed in ie
angular.module('magicApp')
.controller('GenresCtrl', ['$scope', 'BREAKPOINTS', 'Genre',
function ($scope, BREAKPOINTS, Genre) {
$scope.bp = BREAKPOINTS;
$scope.genres = Genre.query();
}]);
Okay, I would move that code you put in link into the controller. The data processing should probably happen in a service. I know you've been told big controllers are bad, but big linking functions are generally worse, and should never do that kind of data processing.
.controller('GenresCtrl', ['$scope', 'BREAKPOINTS', 'Genre',
function ($scope, BREAKPOINTS, Genre) {
$scope.bp = BREAKPOINTS;
$scope.genres = Genre.query().then(function (items) {
angular.forEach(scope.items, function (item) {
item.selected = false;
});
items[0].selected = true;
});
scope.select = function (desiredItem) {
(desiredItem.selected === true) ? desiredItem.selected = false : desiredItem.selected = true;
angular.forEach(scope.items, function (item) {
if (item !== desiredItem) {
item.selected = false;
}
});
};
});
Your link function is now empty. Define items on the rootScope instead, this ensures that the isolateScope and your directive interface are working correctly.
beforeEach(inject(function ($rootScope, $compile, $q) {
elm = angular.element(
'<cc-accordion items="genres"></cc-accordion>'
);
scope = $rootScope;
things = [{
title: 'Scifi',
description: 'Scifi description'
}, {
title: 'Comedy',
description: 'Comedy description'
}];
scope.items = things; // Tests your directive interface
// compile the template?
$compile(elm)(scope);
scope.$digest();
}));
The behavior of the promise should be tested in a controller test, by mocking the return value of the service. Your problem with the $promise test has been solved.
The actual issue was that you were assuming that $q.defer() gave you the same kind of promise as the angular $http, but that is solved by design instead.
As peter said remove the promise from the directive and add it to the controller
angular.module('magicApp')
.controller('MainCtrl', ['$scope', 'Genre',
function ($scope, Genre) {
$scope.genres = Genre.query();
$scope.genres.$promise.then(function () {
angular.forEach($scope.genres, function (genre) {
genre.selected = false;
});
$scope.genres[0].selected = true;
});
}]);
This will also allow the controller to specify which tab is selected to begin with.
In the directive
var ccAccordion = angular.module("ccAccordion", []);
ccAccordion.directive("ccAccordion", function () {
return {
restrict: "AE",
templateUrl: "components/accordion/accordion.html",
scope: {
items: "="
},
link: function (scope) {
scope.select = function (desiredItem) {
(desiredItem.selected === true) ? desiredItem.selected = false : desiredItem.selected = true;
angular.forEach(scope.items, function (item) {
if (item !== desiredItem) {
item.selected = false;
}
});
};
}
};
});
The directive unit test now looks like this
describe('ccAccordion', function () {
var elm, scope, deferred, promise, things;
beforeEach(module('ccAccordion'));
beforeEach(function () {
jasmine.addMatchers({
toHaveClass: function () {
return {
compare: function (actual, expected) {
var classTest = actual.hasClass(expected);
classTest ? classTest = true : classTest = false;
return {
pass: classTest,
message: 'Expected ' + angular.mock.dump(actual) + ' to have class ' + expected
};
}
};
}
});
});
// load the templates
beforeEach(module('components/accordion/accordion.html'));
beforeEach(inject(function ($rootScope, $compile, $q) {
elm = angular.element(
'<cc-accordion items="genres"></cc-accordion>'
);
scope = $rootScope;
scope.genres = [{
title: 'Scifi',
description: 'Scifi description'
}, {
title: 'Comedy',
description: 'Comedy description'
}];
$compile(elm)(scope);
scope.$digest();
}));
it('should create clickable titles', function () {
var titles = elm.find('.cc-accord h2');
expect(titles.length).toBe(2);
expect(titles.eq(0).text().trim()).toBe('Scifi');
expect(titles.eq(1).text().trim()).toBe('Comedy');
});
it('should bind the content', function () {
var contents = elm.find('.cc-accord-content div:first-child');
expect(contents.length).toBe(2);
expect(contents.eq(0).text().trim()).toBe('Scifi description');
expect(contents.eq(1).text().trim()).toBe('Comedy description');
});
it('should change active content when header clicked', function () {
var titles = elm.find('.cc-accord h2'),
divs = elm.find('.cc-accord');
// click the second header
titles.eq(1).find('a').click();
// second div should be active
expect(divs.eq(0)).not.toHaveClass('active');
expect(divs.eq(1)).toHaveClass('active');
});
});
And the unit test for main controller now has the added property of selected
'use-strict';
describe('magicApp controllers', function () {
// using addMatcher because $resource is not $http and returns a promise
beforeEach(function () {
jasmine.addMatchers({
toEqualData: function () {
return {
compare: function (actual, expected) {
return {
pass: angular.equals(actual, expected)
};
}
};
}
});
});
beforeEach(module('magicApp'));
beforeEach(module('magicServices'));
describe('MainCtrl', function () {
var scope, ctrl, $httpBackend;
beforeEach(inject(function (_$httpBackend_, $rootScope, $controller) {
$httpBackend = _$httpBackend_;
$httpBackend.expectGET('/api/genres').
respond([{title: 'Scifi', selected: true}, {title: 'Comedy', selected: false}]);
scope = $rootScope.$new();
ctrl = $controller('MainCtrl', {$scope: scope});
}));
it('should create "genres" model with 2 genres fetched from xhr', function () {
expect(scope.genres).toEqualData([]);
$httpBackend.flush();
expect(scope.genres).toEqualData(
[{title: 'Scifi', selected: true}, {title: 'Comedy', selected: false}]);
});
});
});