I am struggle with create proper unit tests for the angularjs (v1.4.9) application which contains both javascript files (with jasmine tests) and typescript files (with no tests at all, now I am trying to use Mocha, but it can be any framework).
Hence it hybrid and an old angularjs without modules, I decided to pile all .ts to one bundle.js
file, due to avoid files ordering problem (which occurs when I have single .js file per .ts and inject it with gulp task to index.html
).
My tsconfig.js:
{
"pileOnSave": true,
"pilerOptions": {
"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 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 am 'injecting' this module to 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 am trying to run mocha
tests with mand:
./node_modules/.bin/mocha --pilers 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._pile (module.js:643:30)
at Module.m._pile (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._pile (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 it is because I am using amd
module to pile my typescript to one js file, but I don't really know how to fix it. Or if it can't be fixed maybe you have some advices how to 'marrige' the type script to existing AngularJs solution.
Ps. I am using mocha with backed typescript piler, because I have no idea how to run jasmine tests with this bination.
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>
I am struggle with create proper unit tests for the angularjs (v1.4.9) application which contains both javascript files (with jasmine tests) and typescript files (with no tests at all, now I am trying to use Mocha, but it can be any framework).
Hence it hybrid and an old angularjs without modules, I decided to pile all .ts to one bundle.js
file, due to avoid files ordering problem (which occurs when I have single .js file per .ts and inject it with gulp task to index.html
).
My tsconfig.js:
{
"pileOnSave": true,
"pilerOptions": {
"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 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 am 'injecting' this module to 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 am trying to run mocha
tests with mand:
./node_modules/.bin/mocha --pilers 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._pile (module.js:643:30)
at Module.m._pile (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._pile (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 it is because I am using amd
module to pile my typescript to one js file, but I don't really know how to fix it. Or if it can't be fixed maybe you have some advices how to 'marrige' the type script to existing AngularJs solution.
Ps. I am using mocha with backed typescript piler, because I have no idea how to run jasmine tests with this bination.
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>
Share
edited Jan 31, 2018 at 14:37
Michał Jarzyna
asked Jan 26, 2018 at 16:49
Michał JarzynaMichał Jarzyna
1,91618 silver badges29 bronze badges
2
- Post the code connectedCustomersList.controller – Sajeetharan Commented Jan 31, 2018 at 3:49
- Hi, thanks for the ment, actually I have posted this code in section "example of tested class" – Michał Jarzyna Commented Jan 31, 2018 at 8:53
2 Answers
Reset to default 5 +100Hence it hybrid and an old angularjs without modules
You have stated that you are not using modules but you in fact you are.
The tsconfig.json
you have shown indicates that you have configured TypeScript to transpile your code to AMD modules. Furthermore, your index.html
is set up accordingly as you are in fact using an AMD loader, namely RequireJS.
All of this is well and good. You should use modules and doing so with AngularJS is not only possible but easy.
However, ts-node, which is great by the way, takes your TypeScript code, and then automatically transpiles and runs it. When it does this, it loads the settings from your tsconfig.json
, instantiates a TypeScript piler passing those settings, piles your code, and then passes the result to Node.js for execution.
NodeJS is not an AMD module environment. It does not support AMD and does not provide a define
function.
There are several valid ways to execute your tests.
One option is to use different configuration for ts-node, specifically, tell it to output CommonJS modules instead of AMD modules. This will produce output that Node.js understands.
Something like
./node_modules/.bin/mocha --pilers ts:ts-node/register --project tsconfig.tests.json
where tsconfig.tests.json
looks like
{
"extends": "./tsconfig.json",
"pilerOptions": {
"module": "monjs",
"esModuleInterop": true
},
"include": ["tests/**/*.spec.ts"]
}
Bear in mind that AMD and CommonJS modules have different semantics and, while it is you will likely never hit any of their differences in your test code, your code will using different loaders for your tests than your production code.
Another option is to use an AMD pliant loader in node to run your tests. You might be able to do this with mocha's --require option. e.g.
mocha --require requirejs
Remarks:
You have some mistakes in your code that should be addressed even if they are not the direct cause of your issue, they relate to modules, paths, and the like.
Do not use
/// <reference path="..."/>
to load declaration files. The piler will pick them up automatically.Do not use the
module
keyword to create namespaces in your TypeScript code. This is long deprecated and was removed because it introduced terminological confusion. Use thenamespace
keyword instead.Never mix module syntax,
import x from 'y'
, and/// <reference path="x.ts"/>
to actually load code.In other words, in your test, replace
///<reference path="../../../wwwroot/app/domain/paymentConditions/connectedCustomersList/connectedCustomersList.controller.ts"/>
with
import "../../../wwwroot/app/domain/paymentConditions/connectedCustomersList/connectedCustomersList.controller.ts";
at once!
After this change, your test will look like
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 about, just do it.
Consider converting your entire code base to proper modules. AngularJS works fine with this approach and it will reduce overall plexity in your toolchain while making your system better factored and your code easier to maintain and reuse.
The idea would be to eventually change
namespace PaymentConditions { angular.module('app.paymentConditions', ['ui.router', 'ui.bootstrap']); }
to
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 being
export default class ConnectedCustomersListController { static $inject = ['paymentCondition']; name: string; constructor(public paymentCondition: PaymentConditionModel) { this.name = paymentCondition.Name; this.bindData(); } bindData() { // do something } }
Thanks to Aluan Haddad I have solution without any refactor of old Javascript
code, which has not got modules (any kind) nor special node.js loader. All .js
script files was included to index.html
and I would not like to change it. Here I present all mistakes which I have to fix before my tests started to work:
Modules and Namespaces
Because my application hasn't got a module loader I could not use any kind of typescript modules without messing files references. The interesting fact is, the typescript piler is treating 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 in the whole 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);
}
References to files
The main mistake was to mix ///<reference
with some imports like import angular
or import angular from angular
as dependencies between files. It was not a good idea. As I mentioned before - typescript treats all files with import
keyword on the beginning of the file as module
and because of I am using namespaces
I had to change all dependencies to ///<reference
:
///<reference path="../../../../node_modules/@types/angular/index.d.ts"/>
module PaymentCondition {
angular.module('app.paymentConditions', ['ui.router', 'ui.bootstrap']);
}
Order of including files
Now when I have valid .ts
files I can pile them to one bundle.js
. I have no worries about correct files ordering because of proper ///<reference...
declarations. To be able to pile all .ts
to one .js
I had to choose amd
modules as default js
output modules.
{
"pileOnSave": true,
"pilerOptions": {
"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 start my application I only need to add my bundle.js
file to index.html
<script src="bundle.js"></script>
Git ignore
Of course file bundle.js
and bundle.js.map
also can be added to git .ignore
because they are auto-generated. It really helps to avoid often merge conflicts :)
Unit Testing
My second big mistake was decision to use Mocha
as unit test runner. This is node.js
runner and to test angular
application without workarounds is remended to use some browser-like solution. I decided to use Chutzpath (because already have it in project for javascript unit testing) with jasmine
For this purpose I have added new tsconfig.test.js
which includes all .spec.ts
test files and .ts
app files and pile them together in some git .ignore
path as separated files because this is the easiest readable by chuzpath config way (TODO: add tsconfig example)
next I only included this path to chutzpath config and the magic works- all typescript tests now are running like a charm. (TODO: add chutzpath config example).
Gulp
In this solution all .ts
files must be piled before tests run. To automate this operation I am using gulp watch
(TODO: add gulp watcher configuration)