karma-typescript
Karma :heart: Typescript
- Run unit tests written in Typescript with full type checking, seamlessly without extra build steps or scripts.
- Get remapped test coverage with Istanbul.
- Use plain Typescript or a framework: Angular, AngularJS, React, Sinon, any framework of choice.
Installation
The easiest way is to keep karma-typescript
as a devDependency in package.json
:
{
"devDependencies": {
"karma": "^6.1.0",
"karma-typescript": "5.3.0"
}
}
Do this by installing the plugin via npm:
npm install --save-dev karma-typescript
Configuration
Bare minimum configuration can be achieved with the following karma.conf.js
file:
module.exports = function(config) {
config.set({
frameworks: ["jasmine", "karma-typescript"],
files: [
"src/**/*.ts"
],
preprocessors: {
"**/*.ts": "karma-typescript"
},
reporters: ["progress", "karma-typescript"],
browsers: ["Chrome"]
});
};
The above example will compile all Typescript files and run the unit tests, producing remapped coverage in ./coverage
.
Examples
Cookbook
Frameworks and Integrations
Other examples
Sample applications by users
Transforms:
Example output
Advanced configuration
The plugin has default settings for the compiler, instrumenting files and creating reports etc, which should suit most needs.
These are the default compiler settings:
karmaTypescriptConfig: {
compilerOptions: {
emitDecoratorMetadata: true,
experimentalDecorators: true,
jsx: "react",
module: "commonjs",
sourceMap: true,
target: "ES5"
},
exclude: ["node_modules"]
}
If the defaults aren't enough, the settings can be configured from karma.conf.js
:
-
karmaTypescriptConfig.bundlerOptions.addNodeGlobals - Boolean indicating whether the global variables
process
and Buffer
should be added to the bundle.
Defaults to true
.
-
karmaTypescriptConfig.bundlerOptions.constants - An object literal with keys/values which will be available as global
variables in the bundle. The keys are expected to be strings and any non-string value will be stringified with JSON.stringify
.
Configuration example:
constants: {
__STRING__: JSON.stringify("abc" + 123),
__BOOLEAN__: true,
"process.env": {
"VARIABLE": "value"
}
}
Code example:
declare var __STRING__: string;
declare var __BOOLEAN__: boolean;
console.log(__STRING__, __BOOLEAN__, process.env.VARIABLE);
-
karmaTypescriptConfig.bundlerOptions.entrypoints - A RegExp
filtering which files loaded by Karma should be executed in
a test run, for example only filenames ending with ".spec.ts": /\.spec\.ts$/
.
This setting can be used to make sure the specs have finished setting up the test environment before other code starts
requiring modules, which otherwise could lead to subtle bugs caused by race conditions.
When filtering file paths, beware that Windows uses \
while UNIX-like systems use /
as path separator.
Defaults to all files, /.*/
.
-
karmaTypescriptConfig.bundlerOptions.exclude - An array of npm module names that will be excluded from the bundle.
-
karmaTypescriptConfig.bundlerOptions.ignore - An array of npm module names that will be bundled as stubs, ie module.exports = {};
.
-
karmaTypescriptConfig.bundlerOptions.noParse - An array of module names that will be bundled without being parsed for dependencies.
-
karmaTypescriptConfig.bundlerOptions.resolve.alias - An object literal where the key is a module name
and the value is a path that will be used when resolving the module.
The key is a string.
-
karmaTypescriptConfig.bundlerOptions.resolve.extensions - An array of file extensions to use, in order, when resolving modules.
Defaults to [".js", ".json", ".mjs", ".ts", ".tsx"]
.
-
karmaTypescriptConfig.bundlerOptions.resolve.directories - An array of directories where modules will be recursively looked up.
Defaults to ["node_modules"]
.
-
karmaTypescriptConfig.bundlerOptions.sourceMap - A boolean indicating whether source maps should be generated for imported modules in the bundle, useful for debugging in a browser.
For more debugging options, please see karmaTypescriptConfig.coverageOptions.instrumentation
.
Defaults to false
.
-
karmaTypescriptConfig.bundlerOptions.transforms - An array of functions altering or replacing compiled Typescript code/Javascript
code loaded from node_modules
before bundling it.
For more detailed documentation on transforms, please refer to the Transforms API section in this document.
-
karmaTypescriptConfig.bundlerOptions.validateSyntax - A boolean indicating whether the syntax of the bundled code should be validated.
Setting this to false
may speed up bundling for large projects with lots of imports from node_modules
.
Defaults to true
.
-
karmaTypescriptConfig.compilerDelay - The number of milliseconds the compiler waits before compiling the project on each run.
For projects with a large number of files it might be necessary to increase this value to make sure the compiler has collected all files before firing.
Defaults to 250 ms.
-
karmaTypescriptConfig.compilerOptions - This setting will override or add to existing compiler options.
Valid options are the same as for the compilerOptions
section in tsconfig.json
, with the
exception of outDir
and outFile
which are ignored since the code is compiled in-memory.
If noEmitOnError
is set to a truthy value, in either tsconfig.json
or in karmaTypescriptConfig.compilerOptions
,
the karma process will exit with ts.ExitStatus.DiagnosticsPresent_OutputsSkipped
if any compilation errors occur.
-
karmaTypescriptConfig.coverageOptions.instrumentation - A boolean indicating whether the code should be instrumented,
set this property to false
to see the original Typescript code when debugging.
Please note that setting this property to true
requires the Typescript compiler option sourceMap
to also be set to true
.
For more debugging options, please see karmaTypescriptConfig.coverageOptions.sourceMap
.
Defaults to true
.
-
karmaTypescriptConfig.coverageOptions.instrumenterOptions - Pass options to the istanbul
instrumenter, ie options supported by istanbul-lib-instrument:
{
coverageVariable: '__coverage__',
preserveComments: false,
compact: true,
esModules: false,
autoWrap: false,
produceSourceMap: false,
ignoreClassMethods: [],
sourceMapUrlCallback: null,
debug: false,
plugins: [
'asyncGenerators',
'bigInt',
'classProperties',
'classPrivateProperties',
'dynamicImport',
'importMeta',
'objectRestSpread',
'optionalCatchBinding',
'flow',
'jsx'
]
};
-
karmaTypescriptConfig.coverageOptions.exclude - A RegExp
object or an array of RegExp
objects for filtering which files should be excluded from coverage instrumentation.
When filtering file paths, beware that Windows uses \
while UNIX-like systems use /
as path separator.
Defaults to /\.(d|spec|test)\.ts$/i
which excludes *.d.ts, *.spec.ts and *.test.ts (case insensitive).
-
karmaTypescriptConfig.coverageOptions.threshold - An object with minimum coverage thresholds. The threshold values can be set on
a global level and on a per-file level, with options to exclude files and directories, and override settings on a per-file basis using globbing patterns.
A positive value will be used as a minimum percentage, for example statements: 50
means that at least 50% of the statements should be covered by a test.
A negative value will be used as a maximum number of uncovered items, for example lines: 10
means that no more than 10 uncovered lines are allowed.
threshold: {
global: {
statements: 100,
branches: 100,
functions: -10,
lines: 100,
excludes: [
"src/foo/**/*.js"
]
},
file: {
statements: -10,
branches: 100,
functions: 100,
lines: 100,
excludes: [
"src/bar/**/*.js"
],
overrides: {
"src/file.js": {
statements: 90
}
}
}
}
-
karmaTypescriptConfig.coverageOptions.watermarks - An object with custom istanbul watermarks. Each value is an array consisting of
a lower and upper bound. If code coverage is above the upper bound, this is considered "healthy", and many reports will print output in green.
If code coverage is below the lower bound, this is considered "unhealthy", and many reports will print output in red. Yellow output is reserved
for coverage in between the lower and upper bound.
watermarks: {
lines: [75, 90],
functions: [75, 90],
branches: [75, 90],
statements: [75, 90]
}
-
karmaTypescriptConfig.exclude - File string patterns to be excluded by the compiler. This property may be an array
or an object
for more fine-grained control.
- Array: The string values will be merged with existing options.
- Object: The string values will be merged with or replace existing options:
{
mode: "merge|replace",
values: ["foo", "bar"]
}
Defaults to ["node_modules"]
.
-
karmaTypescriptConfig.include - File string patterns to be included by the compiler. This property may be an array
or an object
for more fine-grained control.
- Array: The string values will be merged with existing options.
- Object: The string values will be merged with or replace existing options:
{
mode: "merge|replace",
values: ["foo", "bar"]
}
This option is available in Typescript ^2.0.0.
-
karmaTypescriptConfig.reports - The types of coverage reports that should be created when running the tests,
defaults to an html report in the directory ./coverage
.
Reporters are configured as "reporttype": destination
where the destination can be specified in three ways:
- A
string
with a directory path, for example "html": "coverage"
. - An empty string or
null
. Writes the output to the console, for example "text-summary": ""
. This is only possible for a subset of the reports available. - An
object
with more fine-grained control over path and filename:
"cobertura": {
"directory": "coverage",
"subdirectory": "cobertura"
"filename": "coverage.xml",
}
"cobertura": {
"directory": "coverage",
"subdirectory": function(browser) {
return browser.name.toLowerCase().split(' ')[0];
},
"filename": "coverage.xml"
}
karmaTypescriptConfig: {
reports:
{
"cobertura": {
"directory": "coverage",
"filename": "coverage.xml",
"subdirectory": "cobertura"
},
"html": "coverage",
"text-summary": ""
}
}
-
karmaTypescriptConfig.transformPath - A function for renaming compiled file extensions to .js
.
Defaults to renaming .ts
and .tsx
to .js
.
-
karmaTypescriptConfig.tsconfig - A path to a tsconfig.json
file.
The default compiler options will be replaced by the options in this file.
The directory of the tsconfig.json
file will be used as the base path for the Typescript compiler, and if karmaTypescriptConfig.tsconfig
isn't set, the basePath
property of the Karma config will be used as the
compiler base path instead.
-
karmaTypescriptConfig.stopOnFailure - Stop on any compiler error.
By default karma will stop when any typescript compile errors are encountered.
Setting this to false will allow tests to be run when typescript compile errors are present.
Example of a full karmaTypescriptConfig
configuration:
karmaTypescriptConfig: {
bundlerOptions: {
addNodeGlobals: true,
constants: {
__PRODUCTION__: false
},
entrypoints: /\.spec\.(ts|tsx)$/,
exclude: ["react/addons"],
ignore: ["ws"],
noParse: "jquery",
resolve: {
alias: {
"@angular/upgrade/static$": "../bundles/upgrade-static.umd.js"
},
extensions: [".js", ".json"],
directories: ["node_modules"]
},
sourceMap: false,
transforms: [require("karma-typescript-es6-transform")()],
validateSyntax: true
},
compilerDelay: 500,
compilerOptions: {
noImplicitAny: true,
},
coverageOptions: {
instrumentation: true,
instrumenterOptions: {
preserveComments: true
},
exclude: /\.(d|spec|test)\.ts/i,
threshold: {
global: {
statements: 100,
branches: 100,
functions: -10,
lines: 100,
excludes: [
"src/foo/**/*.js"
]
},
file: {
statements: -10,
branches: 100,
functions: 100,
lines: 100,
excludes: [
"src/bar/**/*.js"
],
overrides: {
"src/file.js": {
statements: 90
}
}
}
},
},
exclude: ["broken"],
include: {
mode: "replace",
values: ["**/*.ts"]
},
reports: {
"cobertura": {
"directory": "coverage",
"filename": "cobertura/coverage.xml"
},
"html": "coverage",
"text-summary": ""
},
transformPath: function(filepath) {
return filepath.replace(/\.(ts|tsx)$/, ".js");
},
tsconfig: "./tsconfig.json",
stopOnFailure: false
}
Automatic bundling
Files executed in the test run are bundled into a main bundle, containing dependencies required from node_modules,
and several smaller standalone bundles containing the Typescript files. All these bundles are tied together by commonjs.js
,
which acts as a hub, loading modules when they are required by other modules.
All files are bundled by being wrapped in a custom CommonJS wrapper,
which emulates the Node.js module system by injecting the require function, the module object, the exports object,
the __dirname variable and the __filename variable.
For instance, this Typescript sample:
export function exportedFunction(): string {
return "";
}
Would be compiled to the following JavaScript (assuming the compiler option module
is set to commonjs
):
function exportedFunction() {
return "";
}
exports.exportedFunction = exportedFunction;
It would then be wrapped in a CommonJS
wrapper by the bundler:
(function(global){
global.wrappers['/absolutepath/app/src/file.ts']=[function(require,module,exports,__dirname,__filename){
function exportedFunction() {
return "";
}
exports.exportedFunction = exportedFunction;
},'src/file.ts',{}];
})(this);
(In this example, the source map and a few other statements have been omitted for brevity and the wrapper has been formatted for readability)
After compilation, a simple static analysis is performed to find "import" and "require" statements in the code and if any
dependencies are found, for instance import { Component } from "@angular/core";
, it is added to the main bundle file along
with its dependencies.
If no import or require statements are found in any Typescript file included in the test run, or the compiler option module
is set to another value than commonjs
, automatic bundling will not occur.
This means that no Typescript file will be wrapped in the CommonJS wrapper and the reason behind this is the encapsulation that
the Node.js module system provides. If no module requests any other module, the test run would contain only isolated islands of
code unreachable from the global scope, there would be nothing to execute.
However, this intentional behavior makes it possible to use karma-typescript for projects that use the Typescript module system,
or have the compiler option module
set to another value than commonjs
, or simply put everything on the global scope.
Importing stylesheets and bundling for production
Style files (.css|.less|.sass|.scss) are served as JSON strings to the browser running the tests,
allowing styles to be loaded using the Typescript import statement, ie import "./style/app.scss";
.
This means styles can be imported in order to let, for instance, webpack load the styles with
less-loader or scss-loader etc for bundling later on, without breaking the unit test runner.
Note: JSON required by modules in node_modules will be loaded automatically by the bundler.
The module object
module: {
exports: {},
id: "project-relative-path/bar.ts",
uri: "/absolute-path/project-relative-path/bar.ts"
}
The module.id
will be calculated from the value of module.uri
, relative to the Karma config basePath
value.
Modules exporting an extensible object such as a function or an object literal will also be decorated with
a non-enumerable default
property if module.exports.default
is undefined.
Globals
A full Node.js environment will be provided with global variables and browser shims for builtin core modules:
- __dirname
- __filename
- Buffer
- global
- process
Browser shims
The plugin uses browser-resolve from the browserify tool chain to load the source code from node_modules.
Mocking
Imported modules, local or npm packages, can be mocked using karma-typescript-mock. This feature is available in karma-typescript@^3.0.5
.
Transforms API
The bundler has a public API which lets plugins alter or completely replace code before adding it to the bundle.
For example, a plugin could compile ES2015 JavaScript code to to ES5 syntax, making it possible to import an npm
module
written in ES2015 syntax from a Typescript module directly.
The interface between the bundler and the plugins is a plain array of functions, specified in the configuration property karmaTypescriptConfig.bundlerOptions.transforms
, where each function is considered a transforming plugin.
The plugin functions in the transforms array are asynchronous and adhere to the Node.js callback convention where the first
argument of the callback function is an Error
object or undefined
and the following parameters contains the result.
However, although each function is asynchronous, all functions will be called synchronously one by one in the order they were added to the array, and each function will be called with the result of the previous function, enabling transforms plugin chaining.
Transforms will be executed at two points in the bundling process: right after compilation of the project Typescript files
and when resolving import
and require
statements. This means each transforming function will be called for both
Typescript files and JavaScript files from node_modules
, making each plugin implementation responsible for validating the
context before performing any logic, for example by checking the file name, module name or the existence of an ast object etc.
Each transforming function will be executed before resolving dependencies, which means paths in import
or require
statements
or anywhere in the code can be rewritten before bundling, to fit the Karma execution environment.
Example of a simple inline transforming function replacing the contents of a .css
file, mimicking the behavior of Css Modules:
karmaTypescriptConfig: {
bundlerOptions: {
transforms: [
function(context, callback) {
if(context.module === "./main.css") {
context.source = "module.exports = { color: red };";
return callback(undefined, true);
}
return callback(undefined, false);
}
]
}
}
It is also possible to change the transpiled Typescript (ie the plain JavaScript code) code by using the third callback parameter to tell the Transforms API not to recompile the transpiled code:
karmaTypescriptConfig: {
bundlerOptions: {
transforms: [
function(context, callback) {
if(context.ts) {
context.ts.transpiled = "\n/* istanbul ignore next */\n" + context.ts.transpiled;
return callback(undefined, true, false);
}
return callback(undefined, false);
}
]
}
}
Context
The context object, TransformContext
, is defined here.
Callback
The callback has two signatures, the "boolean" and the "object".
The boolean callback function has three arguments:
- An
Error
object or undefined
- A boolean indicating whether the value of
context.source
has changed or not. - A boolean indicating whether the transformed source should be recompiled. Defaults to true and can be omitted.
The object callback function has two arguments:
- An
Error
object or undefined
- An object literal, which has the following properties:
- A boolean indicating whether the value of
context.source
has changed or not. - A boolean indicating whether the transformed source should be recompiled (Typescript only!).
- A boolean indicating whether a new AST should be created for the transformed source (JavaScript only!).
Requirements
Typescript ^1.6.2 is required.
Versions 1.6.2 - 1.7.5 work but aren't as heavily tested as versions 1.8.10 and up.
Troubleshooting
Error: Can not load "karma-typescript", it is not registered!
Users have reported success by simply deleting the node_modules
folder and then running npm install
again.
Error: Cannot find module 'buffer/' from '.'
Note: this error has been fixed in karma-typescript@^3.0.0`.
This error seems to hit mostly users of with older versions of npm
, where all dependencies don't get pulled in automatically by npm
.
There's a workaround reported by users, which is simply adding the missing dependencies explicitly to package.json
:
npm install --save-dev browser-resolve
npm install --save-dev buffer
npm install --save-dev process
Other workarounds are running npm install
twice or, if possible, upgrading to a newer version of npm
.
These are the environments reported failing/working:
Npm | Node.js | OS | Works |
---|
2.5.18 | 4.4.7 | Unknown | No |
2.14.7 | 4.2.1 | Ubuntu 14.04 | No |
2.14.12 | 4.2.6 | OSX 10.11.3 | No |
2.15.9 | 4.5.0 | OSX 10.11.6 | No |
2.15.9 | 4.6.0 | OSX 10.12.3 | No |
2.15.11 | 4.6.2 | Ubuntu 14.04 | No |
2.15.11 | 4.7.0 | Ubuntu 14.04 | Yes |
3.5.3 | 4.2.1 | Windows 10 | Yes |
3.8.6 | 6.10.0 | Windows Server 2012 R2 | Yes |
3.10.3 | 6.4.0 | OSX 10.11.6 | Yes |
3.10.9 | 6.9.2 | Ubuntu 14.04 | Yes |
4.0.5 | 6.4.0 | OSX 10.11.6 | Yes |
4.1.2 | 7.5.0 | OSX 10.12.2 | No |
4.1.2 | 7.7.3 | Ubuntu 14.04 | Yes |
Licensing
This software is licensed with the MIT license.
© 2016-2021 Erik Barke, Monounity