Angular Testing with AngularJS TypeScript

I am struggling with creating the right unit tests for an angularjs application (v1.4.9) that contains both javascript files (using jasmine tests) and typescript (without tests at all, now I'm trying to use Mocha, but it can be any structure).

Therefore, it is a hybrid and old angularjs without modules, I decided to compile all .ts into a single bundle.js file due to an error when ordering files (what happens when I have one .js file on .ts and enter its gulp index.html task index.html ).

My 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/**/*" ] } 

example of a tested class:

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

My module declaration:

 ///<reference path="../../../../node_modules/@types/angular/index.d.ts"/> 'use strict'; module PaymentCondition { angular.module('app.paymentConditions', ['ui.router', 'ui.bootstrap']); } 

and I 'inject' this module into the main module file, which is already in javascript- App.module.js .:

 (function () { 'use strict'; var module = angular.module('app', [ 'app.paymentCondition', 'ui.router', 'ui.bootstrap', ]); })(); 

and finally my test class:

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

when I try to run mocha tests with the command:

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

I have this exception:

 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. 

I know, because I use the amd module to compile my typescript into a single js file, but I really don't know how to fix it. Or, if it cannot be fixed, perhaps you have some tips on how to "tarnish" the script type of an existing AngularJs solution.

Ps. I use mocha with the typescript fallback compiler because I have no idea how to run jasmine tests with this combination.

My Index.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> 
+7
javascript angularjs typescript mocha tsc
source share
2 answers

Hence it is a hybrid and old angularjs without modules

You stated that you are not using modules, but you really are.

tsconfig.json you tsconfig.json indicates that you have configured TypeScript to transfer your code to AMD modules. In addition, your index.html configured accordingly, since you are actually using the AMD bootloader, namely RequireJS.

All this is good. You must use modules and do it with AngularJS is not only possible, but also simple.

However, ts-node, which, by the way, is good, takes your TypeScript code, and then it automatically wraps and runs it. When he does this, he loads the settings from your tsconfig.json , creates an instance of the TypeScript compiler, passing those settings, compiles your code, and then passes the result to Node.js for execution.

NodeJS is not a modular AMD environment. It does not support AMD and does not provide a define function.

There are several valid ways to run your tests.

One option is to use a different configuration for the ts node, in particular, telling it to display CommonJS modules instead of AMD modules. This will produce the output that Node.js. understands.

Something like

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

where tsconfig.tests.json looks like

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

Remember that AMD and CommonJS modules have different semantics, and although you will most likely never encounter their differences in the test code, other loaders for your tests will be used in your code than in the production code.

Another option is to use an AMD-compatible bootloader in the host to run your tests. You can do this with the mocha --require option. eg

 mocha --require requirejs 

Notes:

There are errors in your code that should be fixed, even if they are not the direct cause of your problem, they are related to modules, paths and the like.

  • Do not use ///<reference path="..."/> to download creatives. The compiler will pick them up automatically.

  • Do not use the module keyword to create namespaces in your TypeScript code. This has long been deprecated and was removed because it introduced terminological confusion. Use the namespace keyword instead.

  • Never mix module syntax, import x from 'y' and ///<reference path="x.ts"/> to actually load the code.

    In other words, in your test, replace

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

    from

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

    one day!

    After this change, your test will look

     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; 

    This is serious. Don’t think, just do it.

  • Consider converting your entire code base into appropriate modules. AngularJS works great with this approach, and it will reduce the overall complexity of your toolchain, making your system more convenient and easier to maintain and reuse code.

    The idea is to ultimately change

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

    in

     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; 

    with your controller

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

Thanks Aluan Haddad I have a solution without any refactoring of the old Javascript code that does not have modules (of any type) or special node.js loader. All .js script files were included in index.html , and I would not want to change it. Here I present all the errors that I have to fix before my tests start working:

Modules and Namespaces

Since my application does not have a module loader, I could not use any typescript modules without links to messaging files. An interesting fact is that the typescript compiler treats all files as modules when they have any import or export keyword at the beginning of the file. So I decided to use namespaces and stick to them throughout the application:

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

or

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

File links

The main error was mixing ///<reference with some import types import angular or import angular from angular as dependencies between files. This was not a good idea. As I mentioned earlier, typescript treats all files with the import keyword at the beginning of the file as module , and because I use namespaces , I had to change all the dependencies to ///<reference :

 ///<reference path="../../../../node_modules/@types/angular/index.d.ts"/> module PaymentCondition { angular.module('app.paymentConditions', ['ui.router', 'ui.bootstrap']); } 

File Inclusion Order

Now that I have valid .ts files, I can compile them into one bundle.js . I'm not worried about the proper file ordering due to the correct ///<reference... declarations. To be able to compile all .ts into a single .js , I had to choose amd modules as js output modules.

 { "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/**/*" ] } 

Now, to run my application, I only need to add the bundle.js file to index.html

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

Git ignore

Of course, the bundle.js and bundle.js.map file can also be added to git .ignore , because they are automatically generated. This really helps to avoid often merging conflicts :)

Device testing

My second big mistake was deciding to use Mocha as a unit test runner. This is a node.js runner, and it is recommended that you use some browser solution to test your angular application without workarounds. I decided to use Chutzpath (because it already has it in the project for testing the javascript module) with jasmine

To this end, I added a new tsconfig.test.js that includes all the test .spec.ts files and .spec.ts files and .ts them together in some git .ignore path as split files, because it is easiest to read chuzpath config way (TODO: add tsconfig example)

next I just included this path in the chutzpath configuration and the magic works - all typescript tests now work like a charm. (TODO: add a sample chutzpath configuration).

Gulp

In this solution, all .ts files must be compiled before running the tests. To automate this operation, I use gulp watch (TODO: add gulp observer configuration)

+1
source share

All Articles