Angular Karma/Jasmine 单元测试不工作

Angular Karma/Jasmine Unit Test not working

我正在尝试为我的新 Angular 应用程序编写单元测试,但遇到了麻烦。下面是我的控制器。

'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();
});

下面是我正在尝试的测试。

'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);
    });

});

上述期望不成立,因为 $scope.countries 未定义。以下是错误信息。

TypeError: 'undefined' is not an object (evaluating 'result.data')

此外,我看到测试由于某种奇怪的原因被调用了两次。下面是我的 Karma 配置文件。

// 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
});
};

最近几天我一直在写单元测试,因为我发现它们很混乱而且不像 Java 中那样简单。将不胜感激。

The above expectation doesn't work because $scope.countries is undefined

^^ 这不是真的。不是 $scope.countries 未定义,而是 result 未定义,也不是您要分配给 $scope.countriesresult,而是与 $scope.timezones

我认为这是你的问题:

MockTimezone.getTimezones.and.returnValue($q.resolve());

您将 undefined 隐式传递给 resolve() 函数,这会在您实例化控制器时引发错误。 它会抛出该错误,因为您在控制器的末尾有这一行:

$scope.loadTimezones();

正是出于这个原因,我停止了在它们内部初始化控制器。现在我使用从 HTML 启动的 ng-init 来完成它。如果你和我一样修改,以后就不会再遇到这样的问题了。