我正在努力为 angularjs (v1.4.9) 应用程序创建适当的单元测试,该应用程序包含 javascript 文件(带有 jasmine tests)和打字稿文件(根本没有测试,现在我正在尝试使用 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:ProjectsApp.FrontendEasyFrontendsrcEasyFrontendtestspaymentConditionsconnec
edCustomersListconnectedCustomersList.controller.spec.ts:5:1)
at Module._compile (module.js:643:30)
at Module.m._compile (C:ProjectsApp.FrontendEasyFrontendsrcEasyFrontendnode_modulests-nodesrcindex.
s:422:23)
at Module._extensions..js (module.js:654:10)
at Object.require.extensions.(anonymous function) [as .ts] (C:ProjectsApp.FrontendEasyFrontendsrcEasyFr
ntendnode_modulests-nodesrcindex.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:ProjectsApp.FrontendEasyFrontendsrcEasyFrontendnode_modulesmochalibmocha.js:231:27
at Array.forEach (<anonymous>)
at Mocha.loadFiles (C:ProjectsApp.FrontendEasyFrontendsrcEasyFrontendnode_modulesmochalibmocha.js:2
8:14)
at Mocha.run (C:ProjectsApp.FrontendEasyFrontendsrcEasyFrontendnode_modulesmochalibmocha.js:536:10)
at Object.<anonymous> (C:ProjectsApp.FrontendEasyFrontendsrcEasyFrontendnode_modulesmochabin_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 解决方案中。
附言。我正在使用带有支持的打字稿编译器的摩卡,因为我不知道如何使用这种组合运行茉莉花测试。
我的索引.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>
因此它是混合的,并且是没有模块的旧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 标准的加载程序来运行测试。你也许可以使用摩卡的--require选项来做到这一点。例如
mocha --require requirejs
言论:
代码中有一些错误,即使它们不是问题的直接原因,也应该解决这些错误,它们与模块、路径等有关。
不要使用
/// <reference path="..."/>
加载声明文件。编译器将自动拾取它们。不要使用
module
关键字在 TypeScript 代码中创建命名空间。这早已被弃用并被删除,因为它引入了术语混淆。请改用namespace
关键字。切勿混合使用模块语法、
import x from 'y'
和/// <reference path="x.ts"/>
来实际加载代码。换句话说,在您的测试中,替换
///<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 } }
感谢Aluan Haddad,我有了没有重构旧Javascript
代码的解决方案,它没有模块(任何类型的)也没有特殊的节点.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);
}
对文件的引用
主要错误是将///<reference
与某些导入(如import angular
或import angular from angular
)混合为文件之间的依赖关系。这不是一个好主意。正如我之前提到的 - 打字稿将文件开头带有import
关键字的所有文件视为module
并且由于我正在使用namespaces
我不得不将所有依赖项更改为///<reference
:
///<reference path="../../../../node_modules/@types/angular/index.d.ts"/>
module PaymentCondition {
angular.module('app.paymentConditions', ['ui.router', 'ui.bootstrap']);
}
包含文件的顺序
现在,当我有有效的.ts
文件时,我可以将它们编译为一个bundle.js
。由于正确的///<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
运行器,为了测试angular
应用程序而不使用解决方法,建议使用一些类似浏览器的解决方案。我决定使用Chutzpath(因为已经在javascript单元测试项目中使用了它)和jasmine。
为此,我添加了新的tsconfig.test.js
,其中包括所有.spec.ts
测试文件和.ts
应用程序文件,并将它们作为单独的文件在某个 git.ignore
路径中一起编译,因为这是最容易通过 chuzpath 配置方式读取的(TODO:添加 tsconfig 示例)
接下来,我只包括了这个路径到chutzpath配置和魔术工作 - 所有打字稿测试现在都像魅力一样运行。(TODO:添加 chutzpath 配置示例)。
吞掉
在此解决方案中,必须在运行测试之前编译所有.ts
文件。为了自动执行此操作,我正在使用gulp watch
(TODO:添加gulp观察器配置)