【问题标题】:AngularJS TypeScript unit testingAngularJS TypeScript 单元测试
【发布时间】:2018-07-06 01:22:46
【问题描述】:

我正在努力为 angularjs (v1.4.9) 应用程序创建适当的单元测试,该应用程序包含 javascript 文件(带有 jasmine 测试)和 typescript 文件(根本没有测试,现在我正在尝试使用 Mocha,但它可以是任何框架)。

因此它是混合的和没有模块的旧 angularjs,我决定将所有 .ts 编译为一个 bundle.js 文件,以避免文件排序问题(当我每个 .ts 有一个 .js 文件并将其注入时会发生这种情况gulp 任务到index.html)。

我的 tsconfig.js:

{
    "compileOnSave": true,
    "compilerOptions": {
        "noImplicitAny": false, 
        "removeComments": false,
        "outFile": "./wwwroot/bundle.js",
        "sourceMap": true,
        "inlineSources": true,
        "module": "amd",
        "moduleResolution": "node",
        "target": "es5",
        "sourceRoot": "./wwwroot"        
    },
    "include": [
      "wwwroot/app/**/*"
    ],
    "exclude": [
      "node_modules/**/*",
      "tests/**/*"      
    ]
}

测试类示例:

///<reference path="../models/paymentCondition.model.ts"/>
///<reference path="../../../../../node_modules/@types/angular/index.d.ts"/>

'use strict';


module PaymentCondition {

    export class ConnectedCustomersListController {
        name: string;

        static $inject = ['paymentCondition'];
        constructor(private paymentCondition: PaymentConditionModel) {
            this.name = paymentCondition.Name;
            this.bindData();
        }



        bindData() {
            // do something
        }                
    }

    angular
        .module('app.paymentConditions')
        .controller('ConnectedCustomersListController', ConnectedCustomersListController);
}

我的模块声明:

///<reference path="../../../../node_modules/@types/angular/index.d.ts"/>

'use strict';

module PaymentCondition {

    angular.module('app.paymentConditions', ['ui.router', 'ui.bootstrap']);
}

我正在将此模块“注入”到主模块文件中,该文件已经在 javascript-App.module.js.:

(function () {
    'use strict';

    var module = angular.module('app', [       
        'app.paymentCondition',
        'ui.router',     
        'ui.bootstrap',        
    ]);

})();

最后是我的测试课:

///<reference path="../../../node_modules/@types/angular/index.d.ts"/>
///<reference path="../../../wwwroot/app/domain/paymentConditions/connectedCustomersList/connectedCustomersList.controller.ts"/>
///<reference path="../../../node_modules/@types/angular-mocks/index.d.ts"/>

import { expect } from 'chai';
import "angular-mocks/index";
import * as angular from "angular";


describe("app.paymentConditions.connectedCustomersList", () => {
    var mock;
    // inject main module
    beforeEach(angular.mock.module('app.paymentConditions'));
    beforeEach(angular.mock.inject(($controller: ng.IControllerService) => {

        mock = {           
            connectedCustomersListModel: {
                columnDefinitions() {
                }
            },
            paymentCondition: {},
            createController(): PaymentCondition.ConnectedCustomersListController {
                return $controller<PaymentCondition.ConnectedCustomersListController >('ConnectedCustomersListController', {
                    connectedCustomersListModel: mock.connectedCustomersListModel,

                });
            }
        };
    }));

    describe("ConnectedCustomersListController", () => {
        var controller: PaymentCondition.ConnectedCustomersListController;
        beforeEach(() => {
            controller = mock.createController();
        });

        it("should be defined", () => {
            expect(controller).not.undefined;
        });
    });
});

当我尝试使用命令运行mocha 测试时:

./node_modules/.bin/mocha --compilers ts:ts-node/register ./tests/**/*.spec.ts

我有这个例外:

ReferenceError: define is not defined
    at Object.<anonymous> (C:\Projects\App.Frontend\EasyFrontend\src\EasyFrontend\tests\paymentConditions\connec
edCustomersList\connectedCustomersList.controller.spec.ts:5:1)
    at Module._compile (module.js:643:30)
    at Module.m._compile (C:\Projects\App.Frontend\EasyFrontend\src\EasyFrontend\node_modules\ts-node\src\index.
s:422:23)
    at Module._extensions..js (module.js:654:10)
    at Object.require.extensions.(anonymous function) [as .ts] (C:\Projects\App.Frontend\EasyFrontend\src\EasyFr
ntend\node_modules\ts-node\src\index.ts:425:12)
    at Module.load (module.js:556:32)
    at tryModuleLoad (module.js:499:12)
    at Function.Module._load (module.js:491:3)
    at Module.require (module.js:587:17)
    at require (internal/module.js:11:18)
    at C:\Projects\App.Frontend\EasyFrontend\src\EasyFrontend\node_modules\mocha\lib\mocha.js:231:27
    at Array.forEach (<anonymous>)
    at Mocha.loadFiles (C:\Projects\App.Frontend\EasyFrontend\src\EasyFrontend\node_modules\mocha\lib\mocha.js:2
8:14)
    at Mocha.run (C:\Projects\App.Frontend\EasyFrontend\src\EasyFrontend\node_modules\mocha\lib\mocha.js:536:10)
    at Object.<anonymous> (C:\Projects\App.Frontend\EasyFrontend\src\EasyFrontend\node_modules\mocha\bin\_mocha:
82:18)
    at Module._compile (module.js:643:30)
    at Object.Module._extensions..js (module.js:654:10)
    at Module.load (module.js:556:32)
    at tryModuleLoad (module.js:499:12)
    at Function.Module._load (module.js:491:3)
    at Function.Module.runMain (module.js:684:10)
    at startup (bootstrap_node.js:187:16)
    at bootstrap_node.js:608:3
npm ERR! Test failed.  See above for more details.

我知道这是因为我正在使用amd 模块将我的打字稿编译成一个 js 文件,但我真的不知道如何修复它。或者,如果它无法修复,也许您有一些建议如何将类型脚本“结合”到现有的 AngularJs 解决方案。

附言。我正在使用带有支持 typescript 编译器的 mocha,因为我不知道如何使用这种组合运行 jasmine 测试。

我的索引.html:

<!DOCTYPE html>
<html>

<head ng-controller="AppCtrl">
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta lang="da" />
    <title>{{ Page.title() }}</title>


   <!-- endbuild -->
    <!-- inject:css -->
    <link rel="stylesheet" type="text/less" href="less/site.less" />
    <!-- endinject -->
    <!-- build:remove -->
    <script src="less/less.js"></script>
    <!-- endbuild -->    
    <!-- bower:js -->
    <script src="lib/jquery/dist/jquery.js"></script>
    <script src="lib/bootstrap/dist/js/bootstrap.js"></script>
    <script src="lib/angular/angular.js"></script>
    <script src="lib/toastr/toastr.js"></script>
    <script src="lib/angular-ui-router/release/angular-ui-router.js"></script>
    <script src="lib/angular-ui-grid/ui-grid.js"></script>
    <script src="lib/angular-bootstrap/ui-bootstrap-tpls.js"></script>
    <script src="lib/sugar/release/sugar-full.development.js"></script>
    <script src="lib/ng-context-menu/dist/ng-context-menu.js"></script>
    <script src="lib/ng-messages/angular-messages.js"></script>
    <script src="lib/bootstrap-datepicker/dist/js/bootstrap-datepicker.min.js"></script>
    <script src="lib/bootstrap-datepicker/dist/locales/bootstrap-datepicker.da.min.js"></script>
    <script src="lib/angular-ui-tree/dist/angular-ui-tree.js"></script>
    <script src="lib/angular-sanitize/angular-sanitize.js"></script>
    <script src="lib/color-hash/dist/color-hash.js"></script>
    <script src="lib/angular-ui-mask/dist/mask.js"></script>
    <script src="lib/google-maps-js-marker-clusterer/src/markerclusterer.js"></script>
    <script src="lib/ngDraggable/ngDraggable.js"></script>
    <script src="lib/requirejs/require.js"></script>
    <!-- endbower -->
    <!-- endbuild -->
    <!-- build:site_js js/site.min.js -->
    <!-- inject:app:js -- >   
    <script src="bundle.js"></script>
    <script src="app/app.module.js"></script>  
    <script src="app/app.route.config.js"></script>
    <script src="app/app.module.config.js"></script>
    <script src="app/app.constants.js"></script>
    <script src="app/app.appCtrl.js"></script>       
    <!-- endinject -->
    <!-- endbuild -->
    <!-- endbuild -->
    <!-- build:remove -->
    <script src="init.js"></script>
    <!-- endbuild -->    
</head>

<body>
    <div class="fluid-container">
        <ee-global-context-menu></ee-global-context-menu>
        <ui-view></ui-view>
    </div>
</body>
</html>

【问题讨论】:

  • 贴出代码connectedCustomersList.controller
  • 您好,感谢您的评论,实际上我已经在“测试类示例”部分发布了这段代码

标签: javascript angularjs typescript mocha.js tsc


【解决方案1】:

因此它是混合的和没有模块的旧 angularjs

您已声明您不使用模块,但实际上您是。

您显示的tsconfig.json 表明您已将 TypeScript 配置为将代码转换为 AMD 模块。此外,您的index.html 已相应设置,因为您实际上使用的是 AMD 加载程序,即 RequireJS。

这一切都很好。您应该使用模块,并且使用 AngularJS 这样做不仅可行而且容易。

但是,顺便提一下,很棒的 ts-node 会获取您的 TypeScript 代码,然后自动转译并运行它。当它执行此操作时,它会从您的 tsconfig.json 加载设置,实例化一个传递这些设置的 TypeScript 编译器,编译您的代码,然后将结果传递给 Node.js 以执行。

NodeJS 不是 AMD 模块环境。它不支持 AMD,也不提供define 函数。

有几种有效的方法可以执行您的测试。

一种选择是为 ts-node 使用不同的配置,具体来说,告诉它输出 CommonJS 模块而不是 AMD 模块。这将产生 Node.js 可以理解的输出。

类似

./node_modules/.bin/mocha --compilers ts:ts-node/register --project tsconfig.tests.json

tsconfig.tests.json 的样子

{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "module": "commonjs",
    "esModuleInterop": true
  },
  "include": ["tests/**/*.spec.ts"]
}

请记住,AMD 和 CommonJS 模块具有不同的语义,虽然您可能永远不会在测试代码中发现它们的任何差异,但您的代码将使用与生产代码不同的加载器进行测试。

另一种选择是在节点中使用符合 AMD 标准的加载程序来运行您的测试。您可以使用 mocha 的 --require 选项来执行此操作。例如

mocha --require requirejs

备注:

您的代码中有一些错误应该解决,即使它们不是问题的直接原因,它们与模块、路径等有关。

  • 不要使用/// &lt;reference path="..."/&gt; 加载声明文件。编译器会自动拾取它们。

  • 不要使用 module 关键字在 TypeScript 代码中创建命名空间。这早已被弃用并被删除,因为它引入了术语混乱。请改用namespace 关键字。

  • 永远不要混合模块语法、import x from 'y' 和 /// &lt;reference path="x.ts"/&gt; 来实际加载代码。

    换句话说,在你的测试中,替换

    ///<reference path="../../../wwwroot/app/domain/paymentConditions/connectedCustomersList/connectedCustomersList.controller.ts"/>
    

    与

    import "../../../wwwroot/app/domain/paymentConditions/connectedCustomersList/connectedCustomersList.controller.ts";
    

    马上!

    更改后,您的测试将如下所示

    import "../../../wwwroot/app/domain/paymentConditions/connectedCustomersList/connectedCustomersList.controller.ts";
    import chai from 'chai';
    import "angular-mocks/index"; // just like controller.ts
    import angular from "angular";
    const expect = chai.expect;
    

    这很严重。别想了,做吧。

  • 考虑将整个代码库转换为适当的模块。 AngularJS 与这种方法配合得很好,它将降低工具链的整体复杂性,同时使您的系统更好地分解,并使您的代码更易于维护和重用。

    这个想法最终会改变

    namespace PaymentConditions {
      angular.module('app.paymentConditions', ['ui.router', 'ui.bootstrap']);
    }
    

    到

    import angular from 'angular';
    import uiRouter from 'angular-ui-router';
    import uiBootstrap from 'angular-ui-bootstrap';
    
    import ConnectedCustomersListController from './connectedCustomersList/connectedCustomersList.controller';
    
    const paymentConditions = angular.module('app.paymentConditions', [
        uiRouter,
        uiBootstrap
      ])
      .controller({
        ConnectedCustomersListController
      });
    
    export default paymentConditions;
    

    你的控制器是

    export default class ConnectedCustomersListController {
      static $inject = ['paymentCondition'];
    
      name: string;
    
      constructor(public paymentCondition: PaymentConditionModel) {
        this.name = paymentCondition.Name;
        this.bindData();
      }
    
      bindData() {
        // do something
      }                
    }
    

【讨论】:

  • 很好的反馈,现在我正在努力将它应用到我的解决方案中。正如我在const paymentConditions =... 中所了解的那样,我应该从旧的 PaymentConditions 模块声明我的所有控制器、指令和服务吗?并且因为它将在angular.module 中注册所有内容,所以不需要任何特殊步骤即可将此export default paymentConditions 模块注入我的应用程序?当然除了编译 TypeScript 并将 .js 文件包含到我的index.html
  • @MichałJarzyna 好吧,这取决于。您将需要删除您的应用程序脚本标签并使用 RequireJS API 加载您的应用程序。定义主模块的文件可以通过调用angular.bootstrap(document.body, app.name); 来引导它。请注意,转换将是一个复杂的过程。如果您的应用程序很大,请务必小心。
  • 在结束这个赏金之前我有最后一个问题 - 你已经提到,我可以使用 RequireJS API 加载我的应用程序 - 这是很多工作,因为我在 javascrpit 中的应用程序非常大。是否有任何解决方案可以将paymentConditions.module.ts“注入”到我现有的应用程序中?不接触其他文件?
  • 在我的解决方案中,我有主 js 模块,当我注入所有其他模块时,可以说app.module.js。我通过angular.bootstrap('appModule') 调用它。现在 angular 找不到 paymentConditions 模块。当我将 app.module 更改为 ts 并导入 paymentConditions 时,其他文件都在尖叫他们找不到 app.module。最好的方案是将应用程序的其余部分重构为 AMD 支持,并使用 main.js 将旧应用程序加载为 AMD + 编译的 bundle.js 也作为 AMD,但我无法转换 >500 个 js 文件
  • 感谢您,我已经开发出完全可行的解决方案,稍后我将添加全面的描述。您的其他答案也更清楚地说明了当您想将遗留代码采用 TypeScript 时没有很好记录但很重要的案例。真正的英雄:)
【解决方案2】:

感谢 Aluan Haddad 我有解决方案,没有对旧的 Javascript 代码进行任何重构,它没有模块(任何类型),也没有特殊的 node.js 加载器。所有.js 脚本文件都包含在index.html 中,我不想更改它。在这里,我提出了在我的测试开始工作之前我必须修复的所有错误:

模块和命名空间

因为我的应用程序没有模块加载器,所以我不能使用任何类型的打字稿模块而不弄乱文件引用。有趣的事实是,当文件开头有任何 import 或 export 关键字时,打字稿编译器会将所有文件视为模块。所以我决定使用namespaces 并在整个应用程序中坚持使用它们:

namespace PaymentCondition {

    angular.module('app.paymentConditions', ['ui.router', 'ui.bootstrap']);
}

或

namespace PaymentCondition {

    export class ConnectedCustomersListController {
        name: string;

        static $inject = ['paymentCondition'];
        constructor(private paymentCondition: PaymentConditionModel) {
            this.name = paymentCondition.Name;
            this.bindData();
        }



        bindData() {
            // do something
        }                
    }

    angular
        .module('app.paymentConditions')
        .controller('ConnectedCustomersListController', ConnectedCustomersListController);
}

对文件的引用

主要的错误是将///&lt;reference 与import angular 或import angular from angular 等一些导入混合作为文件之间的依赖项。这不是一个好主意。正如我之前提到的 - typescript 将文件开头带有 import 关键字的所有文件视为 module 并且因为我使用的是 namespaces 我不得不将所有依赖项更改为 ///&lt;reference :

///<reference path="../../../../node_modules/@types/angular/index.d.ts"/>

module PaymentCondition {

    angular.module('app.paymentConditions', ['ui.router', 'ui.bootstrap']);
}

包含文件的顺序

现在,当我拥有有效的.ts 文件时,我可以将它们编译为一个bundle.js。由于正确的///&lt;reference... 声明,我不必担心正确的文件排序。为了能够将所有.ts 编译为一个.js,我必须选择amd 模块作为默认js 输出模块。

{
    "compileOnSave": true,
    "compilerOptions": {
        "noImplicitAny": false, 
        "removeComments": false,
        "outFile": "./wwwroot/bundle.js",
        "sourceMap": true,
        "inlineSources": true,
        "module": "amd",
        "moduleResolution": "node",
        "target": "es5",
        "sourceRoot": "./wwwroot"        
    },
    "include": [
      "wwwroot/app/**/*"
    ],
    "exclude": [
      "node_modules/**/*",
      "tests/**/*"      
    ]
}

现在,要启动我的应用程序,我只需将我的 bundle.js 文件添加到 index.html

<script src="bundle.js"></script>

Git 忽略

当然文件bundle.js 和bundle.js.map 也可以添加到git .ignore,因为它们是自动生成的。它确实有助于避免经常合并冲突:)

单元测试

我的第二个大错误是决定使用Mocha 作为单元测试运行器。这是node.js runner,如果要在没有变通方法的情况下测试angular 应用程序,建议使用一些类似浏览器的解决方案。我决定将Chutzpath 与jasmine 一起使用(因为它已经在项目中用于javascript 单元测试)

为此,我添加了新的tsconfig.test.js,其中包括所有.spec.ts 测试文件和.ts 应用程序文件,并将它们一起编译到一些git .ignore 路径中作为单独的文件,因为这是chuzpath 配置最容易读取的文件方式(TODO:添加 tsconfig 示例)

接下来,我只将这条路径添加到 chutzpath 配置和神奇的作品中——现在所有的打字稿测试都像魅力一样运行。 (TODO:添加 chutzpath 配置示例)。

吞咽

在此解决方案中,所有.ts 文件必须在测试运行前编译。为了自动化这个操作,我使用gulp watch(TODO: add gulp watcher configuration)

【讨论】:

    猜你喜欢
    • 2018-07-31
    • 2013-05-27
    • 2018-10-22
    • 2015-07-04
    • 2013-08-29
    • 1970-01-01
    • 2013-04-06
    • 2016-06-19
    • 1970-01-01
    相关资源
    最近更新 更多