I have written one component which post data from some service.
Am not able to cover then function in Unit Test. How to enter test `then' function?
angular.module('myapp')
.component('performAnalysis', {
templateUrl: 'analysis.component.html',
controller: PerformAnalysisController,
controllerAs: 'vm'
});
function PerformAnalysisController(UnitySizerService, SizingService, MarshallDTO, CommonService) {
let vm = this;
vm.$onInit = $onInit;
function $onInit() {
let unitySizerDTO = MarshallDTO.generateDTO();
let previousDTO = CommonService.getProperty('previousDTO');
vm.dataChanged = JSON.stringify(unitySizerDTO) === JSON.stringify(previousDTO);
/* Call Backend API only if DTO is changed */
if (!vm.dataChanged) {
/* Loader On */
vm.activateLoader = true;
SizingService.postSizingResults(unitySizerDTO).then(function (data) {
UnitySizerService.resultSummary = data;
/* New Data coming from Backend */
vm.dataChanged = true;
/* Loader Off */
vm.activateLoader = false;
CommonService.setProperty('previousDTO', unitySizerDTO);
vm.unitySizerService = UnitySizerService;
});
}
else {
vm.unitySizerService = UnitySizerService;
}
}
}
This is test file which I have written, but am not able to cover then function inside this:
describe('my Component', function () {
beforeEach(module('myApp'));
let vm;
let $rootScope;
let $componentController;
let UnitySizerService, SizingService;
beforeEach(inject(function (_$componentController_, _$rootScope_, _UnitySizerService_, _SizingService_) {
$componentController = _$componentController_;
$rootScope = _$rootScope_;
UnitySizerService = _UnitySizerService_;
SizingService = _SizingService_;
vm = $componentController('performAnalysis');
}));
it('should be defined', function () {
expect(vm).toBeDefined();
expect(vm.$onInit).toBeDefined();
expect(UnitySizerService).toBeDefined();
});
it('should show loader', function () {
vm.$onInit();
vm.dataChanged = false;
expect(vm.activateLoader).toBeTruthy();
});
});
In order to mock .then of a Promise in jasmine, you can do something like this
var deferred = $q.defer();
var whatServiceReturns = "test";
// we are returning promise from service function
SizingService.postSizingResults.and.returnValue({$promise:deferred.$promise});
// now let's call the original function
vm.$onInit();
// we will resolve our promise so that we can reach inside 'then'
// here, 'whatServiceReturns' resembles your 'data' parameter in 'then' function
deferred.resolve(whatServiceReturns);
$rootScope.$digest();
// here we can expect things like 'dataChanged' and 'activateLoader' if we see from your example
expect(...).toEqual(...);
You can use deferred.reject(someError); for error.
Edit: added comments to elaborate the code
Related
I've a small controller written in angularJS.
1st function is actually calling a 2nd one to perform a calculation and return it back.
I want to mock the 2nd function in my testing, so that it returns value I've provided in mock instead of calling the function.
ABCService.js
var app = angular.module('mathModule', []);
app.controller('mathService', ['$scope', function($scope){
$scope.first = 0;
$scope.second = 0;
$scope.addTwoNumbers = function(x, y) {
return x + y;
};
$scope.callAddFunction = function() {
return $scope.addTwoNumbers(10, 20);
}
}]);
ABCServic.test.js
require('./mathService.js');
describe('Math service', function() {
beforeEach(
angular.mock.module('mathModule')
);
var $controller;
beforeEach(inject(function(_$controller_) {
$controller = _$controller_;
}));
describe('Test using 2 numbers', function() {
var $scope, controller;
beforeEach(function() {
$scope = {};
controller = $controller('mathService', { $scope: $scope });
});
it("Nested function", function() {
var total = $scope.callAddFunction();
expect(total).toEqual(31);
});
});
});
Here I want to mock addTwoNumbers() so that instead of calling we get value we've provided during testing.
Something like, Mock(addTwoNumbers(x,y)) = 0, so now callAddFunction will return 0 instead of 30, which it should be returning if not mocked.
I am working on Banking app using Angularjs on hackerrank where I am stuck at point.I am trying to call my function from Account summary controller but It is saying that No such function exist in my controller
Here is my code
AccountSummarycontroller.js
// Create the controller AccountSummaryController with getAccountSummary function that access accountSummaryService which is already defined in account-summary.service.js. Use $state for Transition from one page to another.
(function() {
'use strict';
var appContr = angular.module('abcbankApp.accountSummary', ['abcbankApp.accountSummaryService']);
appContr.controller('AccountSummaryController', function(AccountSummaryService) {
var ActSumCtrl = this;
// this.AccountSummaryService.getAccountSummary();
ActSumCtrl.accountList = [];
ActSumCtrl.accountList = AccountSummaryService.getAccountSummary();
});
})();
AccountSumaaryService.js
// Create the service AccountSummaryService with getAccountSummary function which should return the account details from data/account-summary.json using $http.get.
(function() {
'use strict';
var appServc = angular.module('abcbankApp.accountSummaryService', []);
appServc.factory('AccountSummaryService', ['$http',
function($http) {
return {
getAccountSummary: function() {
var data;
$http.get('./data/account-summary.json')
.then(function(response) {
data = response.data;
});
return data;
}
};
}]);
})();
But I am getting error something like below
abcbankApp.accsummary module AccountSummaryController should have a getAccountSummary function FAILED.Expected false to be true.
Testfile.js
describe('AccountSummaryController', function() {
it('Controller definition', function() {
expect(AccountSummaryController).toBeDefined();
});
it('should have a getAccountSummary function', function() {
expect(angular.isFunction(AccountSummaryController.getAccountSummary)).toBe(true);
});
});
});
})();
Any Help will be Appreciated.Thanks in advance
Well to answer your question lets begin with your test case as it is showing that AccountSummaryController.getAccountSummary should be defined
So you should call your function with your controller name and function name
Here is modified code
(function() {
'use strict';
var appContr = angular.module('abcbankApp.accountSummary', ['abcbankApp.accountSummaryService']);
appContr.controller('AccountSummaryController', function(AccountSummaryService) {
var ActSumCtrl = this;
// this.AccountSummaryService.getAccountSummary();
ActSumCtrl.accountList = [];
ActSumCtrl.getAccountSummary=function()
{
//move your logic here
}
ActSumCtrl.accountList = AccountSummaryService.getAccountSummary();
});
})();
Let me know in comment weather it worked or not
I have an angular directive that shows payment history. By default, it shows the last 6 payments using the vm.numberOfPaymentsToDisplay variable. If you click view more, it adds 10. Now, when a user clicks on another section, there's a listener in the link function that is supposed to reset the number to 6, however vm is undefined.
Here's the code:
angular.module('nui.settings2.account')
.directive('paymentHistory', function(){
function PaymentHistoryController(paymentHistoryService, $filter, $window, $translate){
const filter = $filter('formatCurrency');
var vm = this;
vm.payments = paymentHistoryService.get();
vm.numberOfPaymentsToDisplay = 6;
vm.getLastPayment = getLastPayment;
vm.viewMorePayments = viewMorePayments;
vm.title = $translate.instant('NUI.SETTINGS.PAYMENT_HISTORY');
function getLastPayment(){
const lastTransaction = paymentHistoryService.getLastPayment();
return lastPaymentInfo = "amount (date)";
}
function viewMorePayments(){
vm.numberOfPaymentsToDisplay = vm.numberOfPaymentsToDisplay + 10;
return true;
}
}
function link(scope, element, attrs, [expander, paymentHistory]){
const containerEl = element.children();
expander.registerContentContainer(containerEl);
scope.$on(expander.COLLAPSE_EVENT, () => vm.numberOfPaymentsToDisplay = 6);
scope.$on("$destroy", () => scope.$emit(expander.CONTAINER_DEREGISTER_EVENT));
paymentHistory.cancel = () => expander.collapse();
}
return {
restrict: 'E',
templateUrl: 'nui/settings2/account/billing/payment-history.directive.html',
controller: PaymentHistoryController,
link: link,
require: ['^^settingExpander', 'paymentHistory'],
controllerAs: 'PaymentHistoryCtrl',
bindToController: true
};
});
How can I set vm.numberOfPaymentsToDisplay = 6 in the link function even though this knowledge is only known by the controller?
You can add a method to your PaymentHistoryController like setNumberOfPaymentsToDisplay as you inject your PaymentHistoryController into your link function you can call the method like this:
paymentHistory.setNumberOfPaymentsToDisplay(6);
Controller code:
function PaymentHistoryController(paymentHistoryService, $filter, $window, $translate){
const filter = $filter('formatCurrency');
var vm = this;
vm.payments = paymentHistoryService.get();
vm.numberOfPaymentsToDisplay = 6;
vm.getLastPayment = getLastPayment;
vm.viewMorePayments = viewMorePayments;
vm.setNumberOfPaymentsToDisplay = setNumberOfPaymentsToDisplay;
vm.title = $translate.instant('NUI.SETTINGS.PAYMENT_HISTORY');
function getLastPayment(){
const lastTransaction = paymentHistoryService.getLastPayment();
return lastPaymentInfo = "amount (date)";
}
function viewMorePayments(){
vm.numberOfPaymentsToDisplay = vm.numberOfPaymentsToDisplay + 10;
return true;
}
function setNumberOfPaymentsToDisplay(amount) {
vm.numberOfPaymentsToDisplay = amount;
}
}
link code:
function link(scope, element, attrs, [expander, paymentHistory]){
const containerEl = element.children();
expander.registerContentContainer(containerEl);
scope.$on(expander.COLLAPSE_EVENT, () => paymentHistory.setNumberOfPaymentsToDisplay(6));
scope.$on("$destroy", () => scope.$emit(expander.CONTAINER_DEREGISTER_EVENT));
paymentHistory.cancel = () => expander.collapse();
}
Actually you have more options.
the general approach to share data between components is to use a service that is a singleton , a single instance cached and injected by angular everytime you use it inside your component. another valid solution is to emit events.
Service:
.service('MyService', function(){
var data;
this.setData = function(newData){
data = newData;
}
this.getData = function(){
return data;
}
})
Events:
$rootScope.$broadcast('my.evt', data); //down in the scope chain, visible to any scope
$rootScope.$emit('my.evt', data); //up in the scope chain since is the rootscope only visible to rootScope
$scope.$emit //up in the scope chain
$scope.$broacast //down in the scope chain
to listen for events:
$rootScope.$on('my.evt', function(evt, data){ //do something }
or
$scope.$on('my.evt', function(evt, data){ //do something }
However in this case you're using the link function to modify your business logic and this is not the the conventional approach, usually the link is used only to handle dom events and to modify the dom. so my personal advice is to refactor your code and put the whole business logic inside the controller
I am new to AngularJS and Jasmine. Given the following controller, how do I test whether the allPanelsRetrieved() function is called when the $scope.getPanels is triggered?
angular.
module('panelList').
controller('PanelListController', ['Panel', 'PanelSelection', '$scope', '$location', '$uibModal', '$rootScope',
function PanelListController(PanelSelection, $scope, $location, $uibModal, $rootScope) {
$scope.maxAbv = 2;
$scope.minAbv = 12;
this.allPanelsRetrieved = (index, before, filterParams) => {
.....
};
$scope.getPanels = () => {
const filterParams = {};
filterParams.abv_lt = $scope.minAbv;
filterParams.abv_gt = $scope.maxAbv;
$scope.currentPagePanels = this.allPanelsRetrieved (1,[], filterParams);
};
}]).
component('panelList', {
templateUrl: '/components/panel-list/panel-list.template.html',
controller:'PanelListController',
});
Assuming you want allPanelsRetrived to be called, then simply use a boolean.
var bool = false
this.allPanelsRetrieved = (index, before, filterParams) => {
.....
bool=true;
};
$scope.getPanels = () => {
if (bool) {
const filterParams = {};
filterParams.abv_lt = $scope.minAbv;
filterParams.abv_gt = $scope.maxAbv;
$scope.currentPagePanels = this.allPanelsRetrieved (1,[], filterParams);
} else {
// allPanelsRetrieved was not called
}
};
I can see that allPanelsRetrieved seems to be a private(local) method and used inside that controller.
You need not test private(local) methods execution.
If you still want to check if the method is triggered or not you can use jasmine's toHaveBeenCalled() method
execept(myMethod).toHaveBeenCalled();
passes when method is called.
I have a service in which values can change from outside Angular:
angularApp.service('WebSocketService', function() {
var serviceAlarms = [];
var iteration = 0;
this.renderMessages = function(alarms, socket) {
if (! angular.equals(serviceAlarms, alarms)) {
serviceAlarms = alarms;
iteration++;
}
};
this.getAlarms = function () {
return serviceAlarms;
};
this.iteration = function () {
return iteration;
};
this.socket = initSocketIO(this);
});
The initSocketIO function makes callbacks to this services renderMessages() function and serviceAlarms variable gets changed on a steady basis.
Now i am trying to watch for changes in this service like so:
controllers.controller('overviewController', ['$scope', 'WebSocketService', function ($scope, WebSocketService) {
$scope.$watch(
function () {
return WebSocketService.iteration();
},
function(newValue, oldValue) {
$scope.alarms = WebSocketService.getAlarms();
},
true
);
}]);
to no avail. The second function provided to $watch never gets executed except on controller initialization.
I have tried with and without true as third parameter.
You should use $rootScope.$watch (not $scope.$watch)
I ended up using the solution below since $watch didn't work as excpected.
I refactored the solution to use $rootScope in combination with:
angularApp.run(['$rootScope', function($rootScope){
$rootScope.socket = {};
$rootScope.socket.alarms = [];
$rootScope.socket.faults = [];
$rootScope.socket.renderErrors = function(faults, socket) {
var faultArray = [];
angular.forEach(faults, function(error) {
error.value ? faultArray.push(error) : null;
});
if (! angular.equals($rootScope.socket.faults, faultArray)) {
$rootScope.socket.faults = faultArray;
$rootScope.apply();
}
};
$rootScope.socket.renderMessages = function(alarms, socket) {
if (! angular.equals($rootScope.socket.alarms, alarms)) {
$rootScope.socket.alarms = alarms;
$rootScope.$apply();
}
};
$rootScope.socket.socket = initSocketIO($rootScope.socket);
}]);
Now i have my socket-updated-model in all scopes to use freely in controllers and views.
Controller example:
$scope.acknowledgeAlarm = function(alarm) {
$scope.socket.socket.emit('acknowledgeAlarm', {
hash:alarm.icon.hash,
id:alarm.id
});
};
View example:
<div ng-repeat="alarm in socket.alarms">
{{alarm.name}} {{alarm.icon.progress}}
</div>