Reputation: 3134
I'm trying to write unit tests for my new Angular App and have troubles. Below is my controller.
'use strict';
angular.module('nileLeApp')
.controller('RegisterController', function ($scope, $translate, $timeout, vcRecaptchaService, Auth, Country, Timezone, RecaptchaService) {
$scope.success = null;
$scope.error = null;
$scope.doNotMatch = null;
$scope.errorUserExists = null;
$scope.registerAccount = {};
$timeout(function () {
angular.element('[ng-model="registerAccount.email"]').focus();
});
$scope.loadCountries = function () {
Country.getCountries()
.then(function (result) {
$scope.countries = result.data;
});
};
$scope.loadTimezones = function () {
Timezone.getTimezones()
.then(function (result) {
$scope.timezones = result.data;
});
};
// ============ Recaptcha specific code START ===============
$scope.recaptcha = {};
$scope.recaptcha.recaptchaResponse = null;
$scope.recaptchaWidgetId = null;
$scope.setResponse = function (response) {
$scope.recaptcha.recaptchaResponse = response;
$scope.recaptchaMissing = false;
};
$scope.setWidgetId = function (widgetId) {
$scope.recaptchaWidgetId = widgetId;
};
$scope.cbExpiration = function () {
$scope.recaptcha.recaptchaResponse = null;
};
// ============ Recaptcha specific code END ===============
$scope.createAccount = function () {
Auth.createAccount($scope.registerAccount).then(function (response) {
$scope.success = true;
}).catch(function (response) {
$scope.success = false;
});
}
$scope.register = function () {
$scope.recaptchaMissing = false;
$scope.recaptchaInvalid = false;
if ($scope.recaptcha.recaptchaResponse != null) {
RecaptchaService.verify($scope.recaptcha).$promise
.then(function (response) {
if (response.data) {
$scope.createAccount();
} else {
$scope.recaptchaInvalid = true;
vcRecaptchaService.reload($scope.recaptchaWidgetId); // Reload captcha
}
}).catch(function (response) {
});
} else {
$scope.recaptchaMissing = true;
}
};
$scope.loadCountries();
$scope.loadTimezones();
});
Below is the test I'm trying.
'use strict';
describe('Register Controllers Tests', function () {
describe('RegisterController', function () {
// actual implementations
var $scope;
var $q;
// mocks
var MockTimeout;
var MockTranslate;
var MockAuth;
var MockCountry;
var MockTimezone;
// local utility function
var createController;
beforeEach(inject(function ($injector) {
$q = $injector.get('$q');
$scope = $injector.get('$rootScope').$new();
MockTimeout = jasmine.createSpy('MockTimeout');
MockAuth = jasmine.createSpyObj('MockAuth', ['createAccount']);
MockCountry = jasmine.createSpyObj('MockCountry', ['getCountries']);
MockTimezone = jasmine.createSpyObj('MockTimezone', ['getTimezones']);
MockTranslate = jasmine.createSpyObj('MockTranslate', ['use']);
var locals = {
'$scope': $scope,
'$translate': MockTranslate,
'$timeout': MockTimeout,
'Auth': MockAuth,
'Country': MockCountry,
'Timezone': MockTimezone
};
createController = function () {
$injector.get('$controller')('RegisterController', locals);
};
}));
it('should load countries on page load', function () {
var mockCountryResponse = [{
'countryId': 1,
'alpha2Code': "AF",
'countryName': "Afghanistan"
}];
MockCountry.getCountries.and.returnValue($q.resolve(mockCountryResponse));
MockTimezone.getTimezones.and.returnValue($q.resolve());
MockAuth.createAccount.and.returnValue($q.resolve());
// given
createController();
$scope.$apply($scope.loadCountries);
expect($scope.countries).toEqual(mockCountryResponse);
});
});
The above expectation doesn't work because $scope.countries is undefined. Following is the error message.
TypeError: 'undefined' is not an object (evaluating 'result.data')
Also, I see the test getting called twice for some strange reason. Below is my Karma configuration file.
// Karma configuration
// http://karma-runner.github.io/0.10/config/configuration-file.html
module.exports = function (config) {
config.set({
// base path, that will be used to resolve files and exclude
basePath: '../../',
// testing framework to use (jasmine/mocha/qunit/...)
frameworks: ['jasmine'],
// list of files / patterns to load in the browser
files: [
// bower:js
'main/webapp/bower_components/es5-shim/es5-shim.js',
'main/webapp/bower_components/jquery/dist/jquery.js',
'main/webapp/bower_components/angular/angular.js',
'main/webapp/bower_components/angular-animate/angular-animate.js',
'main/webapp/bower_components/angular-aria/angular-aria.js',
'main/webapp/bower_components/angular-bootstrap/ui-bootstrap-tpls.js',
'main/webapp/bower_components/bootstrap/dist/js/bootstrap.js',
'main/webapp/bower_components/angular-bootstrap-nav-tree/dist/abn_tree_directive.js',
'main/webapp/bower_components/angular-file-upload/angular-file-upload.js',
'main/webapp/bower_components/angular-messages/angular-messages.js',
'main/webapp/bower_components/skycons/skycons.js',
'main/webapp/bower_components/angular-skycons/angular-skycons.js',
'main/webapp/bower_components/angular-smart-table/dist/smart-table.min.js',
'main/webapp/bower_components/angular-touch/angular-touch.js',
'main/webapp/bower_components/angular-cache-buster/angular-cache-buster.js',
'main/webapp/bower_components/angular-cookies/angular-cookies.js',
'main/webapp/bower_components/angular-dynamic-locale/src/tmhDynamicLocale.js',
'main/webapp/bower_components/angular-local-storage/dist/angular-local-storage.js',
'main/webapp/bower_components/angular-loading-bar/build/loading-bar.js',
'main/webapp/bower_components/angular-resource/angular-resource.js',
'main/webapp/bower_components/angular-sanitize/angular-sanitize.js',
'main/webapp/bower_components/angular-translate/angular-translate.js',
'main/webapp/bower_components/messageformat/messageformat.js',
'main/webapp/bower_components/angular-translate-interpolation-messageformat/angular-translate-interpolation-messageformat.js',
'main/webapp/bower_components/angular-translate-loader-partial/angular-translate-loader-partial.js',
'main/webapp/bower_components/angular-translate-loader-static-files/angular-translate-loader-static-files.js',
'main/webapp/bower_components/angular-translate-storage-cookie/angular-translate-storage-cookie.js',
'main/webapp/bower_components/angular-translate-storage-local/angular-translate-storage-local.js',
'main/webapp/bower_components/angular-ui-router/release/angular-ui-router.js',
'main/webapp/bower_components/moment/moment.js',
'main/webapp/bower_components/fullcalendar/dist/fullcalendar.js',
'main/webapp/bower_components/angular-ui-calendar/src/calendar.js',
'main/webapp/bower_components/angular-ui-grid/ui-grid.js',
'main/webapp/bower_components/angular-ui-select/dist/select.js',
'main/webapp/bower_components/angular-ui-utils/ui-utils.js',
'main/webapp/bower_components/angular-xeditable/dist/js/xeditable.js',
'main/webapp/bower_components/angularjs-toaster/toaster.js',
'main/webapp/bower_components/angular-strap/dist/angular-strap.js',
'main/webapp/bower_components/angular-strap/dist/angular-strap.tpl.js',
'main/webapp/bower_components/angular-recaptcha/release/angular-recaptcha.js',
'main/webapp/bower_components/bootstrap-daterangepicker/daterangepicker.js',
'main/webapp/bower_components/bootstrap-filestyle/src/bootstrap-filestyle.js',
'main/webapp/bower_components/bootstrap-slider/bootstrap-slider.js',
'main/webapp/bower_components/bootstrap-tagsinput/dist/bootstrap-tagsinput.js',
'main/webapp/bower_components/bootstrap-wysiwyg/bootstrap-wysiwyg.js',
'main/webapp/bower_components/bower-jvectormap/jquery-jvectormap-1.2.2.min.js',
'main/webapp/bower_components/datatables/media/js/jquery.dataTables.js',
'main/webapp/bower_components/flot/jquery.flot.js',
'main/webapp/bower_components/flot-spline/js/jquery.flot.spline.js',
'main/webapp/bower_components/flot.tooltip/js/jquery.flot.tooltip.js',
'main/webapp/bower_components/footable/js/footable.js',
'main/webapp/bower_components/html5sortable/jquery.sortable.js',
'main/webapp/bower_components/json3/lib/json3.js',
'main/webapp/bower_components/ng-grid/build/ng-grid.js',
'main/webapp/bower_components/intl-tel-input/build/js/intlTelInput.min.js',
'main/webapp/bower_components/intl-tel-input/lib/libphonenumber/build/utils.js',
'main/webapp/bower_components/ng-intl-tel-input/dist/ng-intl-tel-input.js',
'main/webapp/bower_components/ngImgCrop/compile/minified/ng-img-crop.js',
'main/webapp/bower_components/ngstorage/ngStorage.js',
'main/webapp/bower_components/ng-file-upload/ng-file-upload.js',
'main/webapp/bower_components/ngInfiniteScroll/build/ng-infinite-scroll.js',
'main/webapp/bower_components/oclazyload/dist/ocLazyLoad.min.js',
'main/webapp/bower_components/screenfull/dist/screenfull.js',
'main/webapp/bower_components/slimscroll/jquery.slimscroll.min.js',
'main/webapp/bower_components/textAngular/dist/textAngular.min.js',
'main/webapp/bower_components/venturocket-angular-slider/build/angular-slider.js',
'main/webapp/bower_components/videogular/videogular.js',
'main/webapp/bower_components/videogular-buffering/buffering.js',
'main/webapp/bower_components/videogular-controls/controls.js',
'main/webapp/bower_components/videogular-ima-ads/ima-ads.js',
'main/webapp/bower_components/videogular-overlay-play/overlay-play.js',
'main/webapp/bower_components/videogular-poster/poster.js',
'main/webapp/bower_components/waves/dist/waves.min.js',
'main/webapp/bower_components/angular-mocks/angular-mocks.js',
// endbower
'main/webapp/scripts/app/app.js',
'main/webapp/scripts/app/**/*.+(js|html)',
'main/webapp/scripts/components/**/*.+(js|html)',
'test/javascript/spec/helpers/module.js',
'test/javascript/spec/helpers/httpBackend.js',
'test/javascript/**/!(karma.conf|protractor.conf).js'
],
// list of files / patterns to exclude
exclude: ['test/javascript/e2e/**'],
preprocessors: {
'./main/webapp/scripts/**/*.js': ['coverage'],
'**/*.html': ['ng-html2js']
},
reporters: ['dots', 'jenkins', 'coverage', 'progress'],
jenkinsReporter: {
outputFile: '../build/test-results/karma/TESTS-results.xml'
},
coverageReporter: {
dir: '../build/test-results/coverage',
reporters: [
{type: 'lcov', subdir: 'report-lcov'}
]
},
// web server port
port: 9876,
// level of logging
// possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
logLevel: config.LOG_INFO,
// enable / disable watching file and executing tests whenever any file changes
autoWatch: false,
// Start these browsers, currently available:
// - Chrome
// - ChromeCanary
// - Firefox
// - Opera
// - Safari (only Mac)
// - PhantomJS
// - IE (only Windows)
browsers: ['PhantomJS'],
// Continuous Integration mode
// if true, it capture browsers, run tests and exit
singleRun: true,
// to avoid DISCONNECTED messages when connecting to slow virtual machines
browserDisconnectTimeout : 10000, // default 2000
browserDisconnectTolerance : 1, // default 0
browserNoActivityTimeout : 4*60*1000 //default 10000
});
};
I'm stuck on writing unit test for last couple days as I find them quite confusing and not simple like in Java. Will appreciate any help.
Upvotes: 1
Views: 1863
Reputation: 3651
The above expectation doesn't work because $scope.countries is undefined
^^ That's not true. It's not that $scope.countries is undefined, it's that result
is undefined, and it's not the result
you're trying to assign to $scope.countries
, it's the one relating to $scope.timezones
I think this is your problem here:
MockTimezone.getTimezones.and.returnValue($q.resolve());
You're implicitly passing undefined into that resolve()
function, and THAT'S throwing an error when you instantiate your controller.
It's throwing that error because you've got this line at the end of your controller:
$scope.loadTimezones();
It's for this reason that I stopped initializing controllers within themselves. Now I do it using ng-init, initiated from the HTML. If you make the same change as I did you won't encounter issues like this again in the future.
Upvotes: 1