I'm working with firebase and angular and I have this function in my controller and I have a test written for it which is passing but I find the test doesn't cover the $loaded portion, How do I test the $loaded part. I used $loaded because firebase returns an object which contains a promise.
Here is the function in my controller
$scope.find = function() {
var uid = $rootScope.currentUser ? ($stateParams.uid || $rootScope.currentUser.uid) : $stateParams.uid;
if (uid) {
console.log(uid, 'uid');
var fellow = User.find(uid);
if (fellow) {
console.log(fellow, 'fellow');
fellow.$loaded(function(data) {
$scope.fellow = data;
$scope.uploadedResult = $scope.fellow.videoUrl;
console.log(data.level, 'level');
if(data.level){
$scope.level = Levels.find(data.level);
}
});
}
}
};
Take Note User is a service that has find method that I'm injecting in my controller .
Here's the test I have which is passing
describe('matsi.controller test', function() {
var User,
scope,
ctrl;
beforeEach(inject(function($controller, $rootScope, $cookies, $injector) {
$httpBackend = $injector.get('$httpBackend');
scope = $rootScope.$new();
Fellow = $injector.get('Fellow');
Log = $injector.get('Log');
MailService = $injector.get('MailService');
stateParams = $injector.get('$stateParams');
rootScope = $injector.get('$rootScope');
Levels = $injector.get('Levels');
User = $injector.get('User');
$location = $injector.get('$location');
rootScope.currentUser = {
uid: 'uid',
fullName: 'Happy fellow'
};
ctrl = $controller('FellowCtrl', {
$scope: scope,
$rootScope: scope
});
$cookies.rootRef = 'https://brilliant-heat-9512.firebaseio.com/';
}));
});
it('should expect find to have been called', function() {
scope.currentUser = {
uid: 'uid'
};
spyOn(User, 'find');
scope.find();
expect(User.find).toHaveBeenCalled();
});
});
Related
I recently started to learn unit test for angular apps. And already faced up with problem. I can not take scope variable from inside executed function. Here is my factory code
angular.module('app').factory('AuthenticationService', AuthenticationService);
AuthenticationService.$inject = ['$http'];
function AuthenticationService($http) {
var service = {};
service.login = login;
return service;
function login(data, callback) {
$http({
method: 'POST',
url: CONFIG.getUrl('auth/login'),
data: data
}).then(function (response) {
callback(response);
}, function (error) {
callback(error);
});
}
Part of my controller file. I only yet wan to test login function
function AuthCtrl($scope, $location, AuthenticationService) {
var vm = this;
vm.login = login;
vm.dataLogin = {
user_id: '',
password: '',
};
function login() {
vm.dataLoading = true;
AuthenticationService.login(vm.dataLogin, function (response) {
if (response.status == 200) {
if (response.data.error_code == 'auth.credentials.invalid') {
vm.invalidCredentials = true;
} else {
vm.invalidCredentials = false;
if (response.data.session_state == 'otp_required') {
vm.userNumber = response.data.user_phone;
$localStorage['session_token'] = response.data.session_token;
vm.needForm = 'someForm';
} else {
AuthenticationService.setCredentials(response.data);
$state.go('dashboard');
}
vm.dataLoading = false;
}
}
});
}
}
});
And my spec.js
describe('AuthCtrl, ', function() {
var $scope, ctrl;
var authSrvMock;
var mockJson = {
user_id: '001',
session_token: 'some_token'
};
var mockLoginData = {
user_id: '0000102',
password: '123456'
};
var mockResponseData = {
data: {
"session_expires": 1453822506,
"session_state": "otp_required",
"session_token": "tokennnn",
"status": "success",
"user_id": "0000102",
"user_phone": "+7 (XXX) XXX-XX-89"
},
status: 200
};
beforeEach(function () {
authSrvMock = jasmine.createSpyObj('AuthenticationService', ['login', 'logout']);
module('app');
inject(function ($rootScope, $controller, $q) {
$scope = $rootScope.$new();
authSrvMock.login.and.returnValue(mockResponseData);
ctrl = $controller('AuthCtrl', {
$scope: $scope,
AuthenticationService: authSrvMock
});
});
});
it('should call login function and pass to dashboard', function () {
ctrl.login();
expect(authSrvMock.login).toHaveBeenCalled();
// until this everything works here just fine
});
});
But after I want to test vm.invalidCredentials, if I will write
expect(ctrl.invalidCredentials).toBe(false)
I will get the error
Expected undefined to be false.
Why I can't see variables?
Bit of a noob myself at Jasmine, but I'm guessing it's because you need to get the promise from your login() to return in Jasmine.
Look into using $q.defer(), or even $httpBackend.
After some more digging process and experiments I found solution.
Here what I did
(function () {
'use strict';
describe('AuthCtrl', function () {
var controller, scope, myService, q, deferred, ctrl;
var mockResponseData = {
response1: {
//...
},
response2: {
//...
},
response3: {
//...
}
};
beforeEach(module('app'));
beforeEach(inject(function ($controller, $rootScope, $q, $httpBackend, AuthenticationService) {
function mockHttp(data, callback) {
deferred = $q.defer();
deferred.promise.then(function (response) {
callback(response);
}, function (error) {
callback(error);
});
}
controller = $controller;
scope = $rootScope.$new();
myService = AuthenticationService;
q = $q;
myService.login = mockHttp;
}));
describe('when returning promises', function () {
beforeEach(function () {
ctrl = controller('AuthCtrl', {
$scope: scope,
myService: myService
});
ctrl.initController();
});
it('shows another form to validate login process', function () {
ctrl.login();
deferred.resolve(mockResponseData.response1);
scope.$digest();
expect(ctrl.invalidCredentials).toBe(false);
expect(ctrl.needForm).toEqual('2sAuth');
expect(ctrl.dataLoading).toBe(false);
});
});
});
})();
Since in my factory almost every method requires data and callback I've created mockHttp functions which takes those arguments and deferred promise. In it block I simply call need function, resolve promise with my prepared answers mock and check my expectations. Everything work. Thanks to for aiming in wich way to look
I am trying to build unit test for my case
I have something in my controller like
$scope.getDetail = function(name) {
//other codes
//using testFactory to get details
testFactory.getDetail(name).then(function(detail){
console.log(detail)
})
//other codes
}
Factory file
var factory = {};
factory.getDetail = function(name) {
//calculate…etc
return details;
}
return factory;
Unit test file
describe('controller', function () {
var testCtrl, scope, testFactory;
beforeEach(module('testApp'));
beforeEach(inject(function (_$controller_, _$rootScope_, _testFactory_) {
scope = _$rootScope_.$new();
testFactory = _testFactory_;
testCtrl = _$controller_('testCtrl', {
$scope: scope
});
};
var name = 'test';
spyOn(testFactory, 'getDetail').and.callFake(function(name) {
return 'success';
});
it('should get details', function() {
var result = testFactory.getDetail(name);
expect(result).toBeTruthy();
})
}));
I am getting
undefined' is not a function (evaluating 'testFactory.getDetail(name)' error.
Can anyone help me to solve this? Not sure what went wrong. Thanks a lot!
code:
$scope.nextStep = function(route) {
session.save($scope.sessionViewModel);
var input = {
emailAddress : session.account.email,
caller : 'USERNAME_EXIST'
};
webServiceDal.doesWebLoginExist(input).success(function(response) {
console.log(response.WebLoginAppResponse.errorFlag);
if ((response.WebLoginAppResponse.errorFlag) && ((response.WebLoginAppResponse.returnCode == 1006) || (response.WebLoginAppResponse.returnCode == 'MSG0307'))) {
$scope.globalError = $scope.validationViewModel.email.existErrorMessage;
}
else
$location.path(route);
});
};
test:
describe('forgotPasswordCtrl', function() {
beforeEach(module('forgotPasswordApp'));
var scope, controller, q, $location, route, deferred, mockSessionService, validationProviderMock, webServDalMock;
beforeEach(function(){
var config = {
urlPath : {
match : ""
}
};
validationProviderMock = {
};
var response = {
};
mockSessionService = {
account : {
email : ""
},
clear : function(){
return true;
}
};
webServDalMock = {
forgotPassword : function(){
deferred = q.defer();
deferred.resolve(response);
return deferred.promise;
},
doesWebLoginExist : function(){
deferred = q.defer();
deferred.resolve(response);
return deferred.promise;
}
};
spyOn(webServDalMock, 'forgotPassword').and.callThrough();
spyOn(webServDalMock, 'doesWebLoginExist').and.callThrough();
spyOn(mockSessionService, 'clear').and.callThrough();
});
beforeEach(inject(function($rootScope, $controller, _$location_, $q){
scope = $rootScope.$new();
$location = _$location_;
q = $q;
controller = $controller('forgotPasswordCtrl', { $scope: scope, webServiceDal : webServDalMock, session : mockSessionService, validationProvider : validationProviderMock });
scope.$apply();
}));
it('should call clear method of session', function(){
scope.cancel();
expect(mockSessionService.clear).toHaveBeenCalled();
});
it('should return the correct url', function(){
scope.cancel();
config.urlPath.match("tfgm_customer");
expect(window.location.assign).toEqual("/web/tfgm_customer");
});
it('asf', function(){
scope.cancel();
config.urlPath.match("tfgm_customerERROR");
expect(window.location.assign).toEqual("/web/tfgm_admin");
});
it('should call webServiceDal', function(input){
scope.finish();
scope.$apply();
expect(webServDalMock.forgotPassword).toHaveBeenCalled();
});
it('should call webServiceDal', function(){
scope.nextStep(route);
scope.$apply();
expect(webServDalMock.doesWebLoginExist).toHaveBeenCalled();
});
});
before each:
beforeEach(inject(function($rootScope, $controller, _$location_, $q){
scope = $rootScope.$new();
$location = _$location_;
q = $q;
controller = $controller('forgotPasswordCtrl', { $scope: scope, webServiceDal : webServDalMock, session : mockSessionService, validationProvider : validationProviderMock });
scope.$apply();
}));
cant work out for the life of me why this is not passing? i have called the correct function and the called the expect correctly. i have other files which i have run identical tests on, the only difference is the naming of variables etc. and they pass.
am i missing something simple?
You problem is that a deferred promise does not return a success function but rather (then, catch or finally), $q docs
You would have to modify your mock doesWebLoginExist to return a success function when called.
EDIT:
Something like
doesWebLoginExist : function(){
return {success: function(cb) {
cb(response);
}};
}
I am trying to write a jasmine test on some javascript using spyon over a method that uses $http. I have mocked this out using $httpBackend and unfortunately the spy doesn't seem to be picking up the fact the method has indeed been called post $http useage. I can see it being called in debug, so unsure why it reports it hasn't been called. I suspect I have a problem with my scope usage ? or order of $httpBackend.flush\verify ?:
Code under test
function FileUploadController($scope, $http, SharedData, uploadViewModel) {
Removed variables for brevity
.....
$scope.pageLoad = function () {
$scope.getPeriods();
if ($scope.uploadViewModel != null && $scope.uploadViewModel.UploadId > 0) {
$scope.rulesApplied = true;
$scope.UploadId = $scope.uploadViewModel.UploadId;
$scope.linkUploadedData();
} else {
$scope.initDataLinkages();
}
}
$scope.initDataLinkages = function () {
$http({ method: "GET", url: "/api/uploadhistory" }).
success(function (data, status) {
$scope.status = status;
$scope.setUploadHistory(data);
}).
error(function (data, status) {
$scope.data = data || "Request failed";
$scope.status = status;
});
}
$scope.setUploadHistory = function (data) {
if ($scope.UploadId > 0) {
$scope.currentUpload = data.filter(function (item) {
return item.UploadId === $scope.UploadId;
})[0];
//Remove the current upload, to prevent scaling the same data!
var filteredData = data.filter(function (item) {
return item.UploadId !== $scope.UploadId;
});
var defaultOption = {
UploadId: -1,
Filename: 'this file',
TableName: null,
DateUploaded: null
};
$scope.UploadHistory = filteredData;
$scope.UploadHistory.splice(0, 0, defaultOption);
$scope.UploadHistoryId = -1;
$scope.UploadTotal = $scope.currentUpload.TotalAmount;
} else {
$scope.UploadHistory = data;
}
}
Test setup
beforeEach(module('TDAnalytics'));
beforeEach(inject(function (_$rootScope_, $controller, _$httpBackend_) {
$rootScope = _$rootScope_;
$scope = $rootScope.$new();
$httpBackend = _$httpBackend_;
var sharedData = { currentBucket: { ID: 1 } };
controller = $controller('FileUploadController', { $scope: $scope, SharedData: sharedData, uploadViewModel: null });
$httpBackend.when('GET', '/api/Periods').respond(periods);
$httpBackend.when('GET', '/api/uploadhistory').respond(uploadHistory);
$scope.mappingData = {
FieldMappings: [testDescriptionRawDataField, testSupplierRawDataField],
UserFields: [testDescriptionUserField, testSupplierUserField]
};
}));
afterEach(function() {
testDescriptionRawDataField.UserFields = [];
testSupplierRawDataField.UserFields = [];
testTotalRawDataField.UserFields = [];
$httpBackend.flush();
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
Working test:
it('pageLoad should call linkUploadedData when user has navigated to the page via the Data Upload History and uploadViewModel.UploadId is set', function () {
// Arrange
spyOn($scope, 'linkUploadedData');
$scope.uploadViewModel = {UploadId: 1};
// Act
$scope.pageLoad();
// Assert
expect($scope.rulesApplied).toEqual(true);
expect($scope.linkUploadedData.calls.count()).toEqual(1);
});
Test that doesn't work (but should. returns count-0 but is called)
it('pageLoad should call setUploadHistory when data returned successfully', function () {
// Arrange
spyOn($scope, 'setUploadHistory');
// Act
$scope.initDataLinkages();
// Assert
expect($scope.setUploadHistory.calls.count()).toEqual(1);
});
The issue is you call httpBackend.flush() after the expect, which means success is called after you do your tests. You must flush before the expect statement.
it('pageLoad should call setUploadHistory when data returned successfully',
inject(function ($httpBackend, $rootScope) {
// Arrange
spyOn($scope, 'setUploadHistory');
// Act
$scope.initDataLinkages();
$httpBackend.flush();
$rootScope.$digest()
// Assert
expect($scope.setUploadHistory.calls.count()).toEqual(1);
}));
You may need to remove the flush statement from after your tests, but it probably should not be there anyway because usually it's a core part of testing behaviour and should be before expect statements.
I can't see why vm.chartData in my HomeCtrl never gets populated with the data i've mocked to it in the beforeEach(). the console.log(scope.vm.chartData) returns undefined even while the other scope vars like graphLoading are defined and changed properly.
describe('HomeCtrl', function () {
var controller, scope, myService, q, $timeout;
beforeEach(module('dashboardApp'));
beforeEach(inject(function ($controller, $rootScope, $q, _$timeout_) {
controller = $controller;
scope = $rootScope.$new();
$timeout = _$timeout_;
myService = jasmine.createSpyObj('Chart', ['get']);
q = $q;
}));
describe('when returning promises', function () {
beforeEach(function () {
myService.get.and.returnValue(q.when( { result:
'Stuff'
}));
controller('HomeCtrl as vm', { $scope: scope, Chart: myService });
scope.$apply();
});
it('test dirty graph init', function () {
expect(scope.vm.graphLoading).toBe(true);
scope.vm.dirtyTestGraph();
scope.$digest();
$timeout.flush();
expect(scope.vm.graphLoading).toBe(false);
console.log(scope.vm.chartData);
});
});
});
relevent code from homectrl
vm.dirtyTestGraph = function() {
vm.graphTitle = 'Deposit Amount';
$timeout(function(){
Chart.get( { interval:'3h', type:'_type:deposit',
from:1416960000000, to:Date.now() } )
.then(function(chart){
vm.graphLoading = false;
vm.chartData = chart.data;
});
}, 2000);
};
and here is the return value of Chart.get in the Chart factory
return $q.all([chartData])
.then(function(data){
var graphData = data[0].data.facets[0].entries;
var newData = [];
graphData.forEach(function(element){
var newElem = {
time: element.time,
deposits: element.total.toFixed(2)
};
newData.push(newElem);
});
return new Chart(newData);
});
Your controller code is looking for a data property in the object within the promise returned by Chart.get:
vm.chartData = chart.data;
But your test's stub is returning an object without a data property:
myService.get.and.returnValue(q.when({
result: 'Stuff'
}));
So vm.chartData gets assigned with undefined.