Karma 测试未加载 AngularJS 工厂

Karma tests are not loading AngularJS factory

我已将 karma 与我现有的项目集成,但我很难弄清楚为什么 karma 测试不加载 MathService 工厂 (math.js)。

文件夹结构

/tests
/www
+ /js
   + app.js
   + /controllers
   + /services
      + math.js
+ /lib

math.test.js

describe('MathService Spec', function() {

    var OfficerValidationService;

    beforeEach(function() {
      angular.module('starter');
    });

    beforeEach(inject(function() {
      var $injector = angular.injector(['starter']);
      OfficerValidationService = $injector.get('MathService');
    }));

    it('is very true', function(){
      //var output = OfficerValidationService.something();
      expect(OfficerValidationService).toBeTruthy();
    });

  });

app.js 模块定义为

var app = angular.module('starter', ['ionic', 'ngCordova', 'pascalprecht.translate', 'textAngular', 'jett.ionic.content.banner', 'barcodeListener'])
  .constant('ENV', {
    mode: 'prod'
  })
  .run(function ($ionicPlatform, $rootScope, $translate,
    $timeout, $localstorage, $ionicHistory, $location, DatabaseService, ModeService, ArrowsysService) { /* code */ })

数学服务

app.factory('MathService', function () {
    return {
        roundNumber: function(toRound){
            if (toRound == 1) {
                return 1;
            } else {
                return 2;
            } 
        }
    },
    function roundNumber(toRound) {
        if (toRound == 1) {
            return 1;
        } else {
            return 2;
        }
    }
});

karma.conf.js

files: [
  'https://ajax.googleapis.com/ajax/libs/angularjs/1.6.7/angular.min.js',
  'https://cdnjs.cloudflare.com/ajax/libs/angular-mocks/1.6.8/angular-mocks.js',
  'www/js/app.js',
  'www/js/services/math.js',
  'tests/*.test.js'
],

结果显示 MathService 未定义。

Chrome 63.0.3239 (Windows 10 0.0.0) MathService Spec is very true FAILED
        Error: [$injector:modulerr] http://errors.angularjs.org/1.6.7/$injector/modulerr?p0=starter&p1=Error%3A%20%5B%24injector%3Amodulerr%5D%20http%3A%2F%2Ferrors.angularjs.org%2F1.6.7%2F%24injector%2Fmodulerr%3Fp0%3Dionic%26p1%3DError%253A%2520%255B%2524injector%253Anomod%255D%2520http%253A%252F%252Ferrors.angularjs.org%252F1.6.7%252F%2524injector%252Fnomod%253Fp0%253Dionic%250A%2520%2520%2520%2520at%2520https%253A%252F%252Fajax.googleapis.com%252Fajax%252Flibs%252Fangularjs%252F1.6.7%252Fangular.min.js%253A7%253A76%250A%2520%2520%2520%2520at%2520https%253A%252F%252Fajax.googleapis.com%252Fajax%252Flibs%252Fangularjs%252F1.6.7%252Fangular.min.js%253A26%253A408%250A%2520%2520%2520%2520at%2520b%2520(https%253A%252F%252Fajax.googleapis.com%252Fajax%252Flibs%252Fangularjs%252F1.6.7%252Fangular.min.js%253A25%253A439)%250A%2520%2520%2520%2520at%2520https%253A%252F%252Fajax.googleapis.com%252Fajax%252Flibs%252Fangularjs%252F1.6.7%252Fangular.min.js%253A26%253A182%250A%2520%2520%2520%2520at%2520https%253A%252F%252Fajax.googleapis.com%252Fajax%252Flibs%252Fangularjs%252F1.6.7%252Fangular.min.js%253A42%253A332%250A%2520%2520%2520%2520at%2520p%2520(https%253A%252F%252Fajax.googleapis.com%252Fajax%252Flibs%252Fangularjs%252F1.6.7%252Fangular.min.js%253A8%253A7)%250A%2520%2520%2520%2520at%2520g%2520(https%253A%252F%252Fajax.googleapis.com%252Fajax%252Flibs%252Fangularjs%252F1.6.7%252Fangular.min.js%253A42%253A180)%250A%2520%2520%2520%2520at%2520https%253A%252F%252Fajax.googleapis.com%252Fajax%252Flibs%252Fangularjs%252F1.6.7%252Fangular.min.js%253A42%253A364%250A%2520%2520%2520%2520at%2520p%2520(https%253A%252F%252Fajax.googleapis.com%252Fajax%252Flibs%252Fangularjs%252F1.6.7%252Fangular.min.js%253A8%253A7)%250A%2520%2520%2520%2520at%2520g%2520(https%253A%252F%252Fajax.googleapis.com%252Fajax%252Flibs%252Fangularjs%252F1.6.7%252Fangular.min.js%253A42%253A180)%0A%20%20%20%20at%20https%3A%2F%2Fajax.googleapis.com%2Fajax%2Flibs%2Fangularjs%2F1.6.7%2Fangular.min.js%3A7%3A76%0A%20%20%20%20at%20https%3A%2F%2Fajax.googleapis.com%2Fajax%2Flibs%2Fangularjs%2F1.6.7%2Fangular.min.js%3A43%3A99%0A%20%20%20%20at%20p%20(https%3A%2F%2Fajax.googleapis.com%2Fajax%2Flibs%2Fangularjs%2F1.6.7%2Fangular.min.js%3A8%3A7)%0A%20%20%20%20at%20g%20(https%3A%2F%2Fajax.googleapis.com%2Fajax%2Flibs%2Fangularjs%2F1.6.7%2Fangular.min.js%3A42%3A180)%0A%20%20%20%20at%20https%3A%2F%2Fajax.googleapis.com%2Fajax%2Flibs%2Fangularjs%2F1.6.7%2Fangular.min.js%3A42%3A364%0A%20%20%20%20at%20p%20(https%3A%2F%2Fajax.googleapis.com%2Fajax%2Flibs%2Fangularjs%2F1.6.7%2Fangular.min.js%3A8%3A7)%0A%20%20%20%20at%20g%20(https%3A%2F%2Fajax.googleapis.com%2Fajax%2Flibs%2Fangularjs%2F1.6.7%2Fangular.min.js%3A42%3A180)%0A%20%20%20%20at%20Object.hb%20%5Bas%20injector%5D%20(https%3A%2F%2Fajax.googleapis.com%2Fajax%2Flibs%2Fangularjs%2F1.6.7%2Fangular.min.js%3A46%3A250)%0A%20%20%20%20at%20UserContext.%3Canonymous%3E%20(http%3A%2F%2Flocalhost%3A9876%2Fbase%2Ftests%2Fmath.test.js%3Ff11444440ebffa5d88f933a1339ba01072b896ce%3A10%3A31)%0A%20%20%20%20at%20Object.invoke%20(https%3A%2F%2Fajax.googleapis.com%2Fajax%2Flibs%2Fangularjs%2F1.6.7%2Fangular.min.js%3A44%3A390)
            at https://ajax.googleapis.com/ajax/libs/angularjs/1.6.7/angular.min.js:7:76
            at https://ajax.googleapis.com/ajax/libs/angularjs/1.6.7/angular.min.js:43:99
            at p (https://ajax.googleapis.com/ajax/libs/angularjs/1.6.7/angular.min.js:8:7)
            at g (https://ajax.googleapis.com/ajax/libs/angularjs/1.6.7/angular.min.js:42:180)
            at Object.hb [as injector] (https://ajax.googleapis.com/ajax/libs/angularjs/1.6.7/angular.min.js:46:250)
            at UserContext.<anonymous> (tests/math.test.js:10:31)
            at Object.invoke (https://ajax.googleapis.com/ajax/libs/angularjs/1.6.7/angular.min.js:44:390)ngularjs/1.6.7/angular.min.js:44:390)                                libs/angular-mocks/1.6.8/angular-mocks.js:3183:20)            at UserContext.WorkFn (https://cdnjs.cloudflare.com/ajax/libs/angular-mocks/1.6.8/angular-mocks.js:3183:20)                   flare.com/ajax/libs/angular-mocks/1.6.8/angular-mocks.js:3146:25)        Error: Declaration Location            at window.inject.angular.mock.inject (https://cdnjs.cloudflare.com/ajax/libs/angular-mocks/1.6.8/angular-mocks.js:3146:25)
            at Suite.<anonymous> (tests/math.test.js:9:16)
            at tests/math.test.js:1:1                                 (0.009 secs / 0 secs)        
 Expected undefined to be truthy.            at UserContext.<anonymous> (tests/math.test.js:16:40)Chrome 63.0.3239 (Windows 10 0.0.0): Executed 1 of 1 (1 FAILED) ERROR (0.009 secs / 0 secs)Terminate batch job (Y/N)?

1) 你应该使用 module function provided by angular-mocks and not angular.module,这样所有的测试都会 运行 在你的模块范围内,这样注入器就会有关于声明和依赖的正确信息;

2) 您可以将 $injector 服务作为 inject() method 回调或类似 var $injector = angular.injector(); 的参数之一传递,但不是您这样做的方式;

3) 最好为您的 MathService 声明自己的 module,因为要 运行 starter 模块范围内的测试,您必须提供/模拟所有依赖项,我认为您的 MathService 不应该依赖于 ionicngCordova.

考虑到以上几点,这里是一个工作示例,说明如何在您的情况下测试服务:

angular.module('starter', ['ionic', 'ngCordova', 'pascalprecht.translate', 'textAngular', 'jett.ionic.content.banner', 'barcodeListener', 'starter.services'])
    .constant('ENV', {
        mode: 'prod'
    })
    .run(function ($ionicPlatform, $rootScope, $translate,
                   $timeout, $localstorage, $ionicHistory, $location, DatabaseService, ModeService, ArrowsysService, MathService) {
        /* code */
    });

angular.module('starter.services', [])
    .factory('MathService', function () {

        function roundNumber(toRound) {
            if (toRound === 1) {
                return 1;
            } else {
                return 2;
            }
        }

        return {
            roundNumber: roundNumber
        }

    });

describe('MathService Spec', function () {

    var MathService;

    beforeEach(module('starter.services'));

    beforeEach(inject(function ($injector) {
        MathService = $injector.get('MathService');
    }));

    it('is very true', function () {
        expect(MathService).toBeTruthy();
    });

    it('roundNumber test', function () {
        expect(MathService.roundNumber(1)).toBe(1);
        expect(MathService.roundNumber(10)).toBe(2);
    });

});
.as-console-wrapper {
  height:0;
}
<!DOCTYPE html>
<html>

  <head>
    <!-- jasmine -->
    <script src="//cdnjs.cloudflare.com/ajax/libs/jasmine/2.8.0/jasmine.js"></script>
    <!-- jasmine's html reporting code and css -->
    <script src="//cdnjs.cloudflare.com/ajax/libs/jasmine/2.8.0/jasmine-html.js"></script>
    <link href="//cdnjs.cloudflare.com/ajax/libs/jasmine/2.8.0/jasmine.css" rel="stylesheet" />
    
    <script src="//cdnjs.cloudflare.com/ajax/libs/jasmine/2.8.0/boot.js"></script>
    <!-- angular itself -->
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.js"></script>
    <!-- angular's testing helpers -->
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular-mocks.js"></script>
  </head>

  <body>
    <!-- bootstrap jasmine! -->
  <script>
    var jasmineEnv = jasmine.getEnv();
    
    // Tell it to add an Html Reporter
    // this will add detailed HTML-formatted results
    // for each spec ran.
    jasmineEnv.addReporter(new jasmine.HtmlReporter());
    
    // Execute the tests!
    jasmineEnv.execute();
  </script>
  </body>

</html>

P.S.: 正如@Petr Averyanov 所提到的,通过 https://docs.angularjs.org/guide/unit-testing 将是您更好地熟悉 AngularJS 单元测试。