Typescript with JSPM code coverage tutorial
In this tutorial we will learn how to implement Karma code coverage to a front-end project that uses SystemJS and JSPM, developed in Typescript (which is becoming increasingly popular in large scale front-end clients).
What is code coverage?
Code coverage or test coverage is essentially the proportion of code that is run by your test suite. It’s essentially a measurement of how much of your code is covered with unit tests.
Why is knowing the level of coverage useful?
Well there are actually a few reasons why you’d want to know covered your code base is.. Not to mention it’s cool to gauge how well you’ve been developing features with TDD (Test Driven Development).
Here are some reasons:
-
Firstly, it can help to identify areas of technical debt within an application and these can then be focused on in any areas of downtime. Adding tests to code will help you later when it comes to refactoring.
-
Secondly, it helps to identify an are which might need a re-write. For example, if a new feature requires changes in an area with no tests to be altered. You might be better of re-writing that particular file/area with a TDD approach.
-
Finally, it helps to reassure both the engineers and clients that the most important conditions (function points) have been tested.
Its important to remember that code coverage isn’t about reaching a quality target (i.e. 100% coverage). It’s more about the journey to get there and finding untested code. In most cases it unrealistic to expect 100% code coverage, especially when dealing with a legacy project.
Code coverage report example
Here’s an example of the coverage report we’re hoping to generate in this tutorial.
Prerequisites & Caveats
Now I am going to assume you know a little about karma, you may already have a karma suite running.. if you don’t, I’d point you to the karma-jspm docs for setting up your unit test suite.
Caveats
Currently at the time of writing this article, if you are using karma-jspm
and giving it typescript files to run the tests - there is no way to implement code coverage. Some clever people are working on it thought so this article might be obsolete in 6 months, (but what isn’t in this industry!).
You’re going to have to the typescript compiler (tsc) to generate some JS files and run the tests on those - which is what we’ll be doing in this tutorial.
How to generate TypeScript code coverage with JSPM
For clarity, I am going to start with the following karma config file:
module.exports = function (config) {
var configuration = {
autoWatch: false,
frameworks: ['jspm', 'mocha', 'chai-sinon', 'chai'],
reporters: ['dots'],
logLevel: config.LOG_ERROR,
plugins: [
'karma-jspm',
'karma-mocha',
'karma-chai',
'karma-chai-sinon',
'karma-chrome-launcher',
],
browserNoActivityTimeout: 90000,
ngHtml2JsPreprocessor: {
stripPrefix: 'src/',
moduleName: 'gulpAngular'
},
basePath: './',
browsers: [
'Chrome'
],
proxies: {
'/.tmp': '/base/.tmp'
},
jspm: {
loadFiles: [
'.tmp/scripts/**/*.spec.js'
],
serveFiles: [
'.tmp/scripts/**/*.js',
'src/**/*.ts'
]
}
};
config.set(configuration);
};
I also have my JSPM loader config file setup using the typescript loader ‘plugin-typescript’ developed by Frank Wallis.
The final piece of this jigsaw comes in the form of a tsconfig.json
file, which will store out compile config and is where we will specify our typescript files. This will look something like the following:
{
"compilerOptions": {
"jsx": "react",
"target": "es5",
"module": "system",
"allowSyntheticDefaultImports": true,
"noImplicitAny": false,
"suppressImplicitAnyIndexErrors": false,
"noImplicitReturns": false,
"sourceMap": true,
"outDir": "./.tmp/scripts"
},
"compileOnSave": false,
"filesGlob": [
"./src/**/*.ts",
"./src/**/*.tsx",
"./typings/index.d.ts"
],
"files": [
"./src/index.spec.ts",
"./src/index.ts",
"./typings/index.d.ts"
],
"atom": {
"rewriteTsconfig": true
}
}
For the purposes of this tutorial and to keep it as simple as possible I only have 2 files in the project. An index.ts
file and an index.spec.ts
file.
You can download the finished and working files here
Getting down to it!
We’ll start by installing some dependancies that we’re going to need:
npm install karma-coverage istanbul --save-dev
As stated before, we can’t use typescript files directly in karma if we want to generate code coverage reports so we’re going to want to use the TypeScript compiler. The best way to do this is to add it to an existing npm
(and if you’re not sure what that is then check out my article on using npm scripts as build tools) task, something like the following:
{
"test": "tsc && karma start karma.conf.js --single-run",
}
Looking back at our tsconfig.json file, you can see that we have defined an outDir
and in this instance where compiling out files to a .tmp
directory. We have then reference this directory in our karma config file as part of the JSPM task. I have highlighted this below:
jspm: {
loadFiles: [
'.tmp/scripts/**/*.spec.js'
],
serveFiles: [
'.tmp/scripts/**/*.js',
'src/**/*.ts'
]
}
In order to get our code coverage working, we first need to add karma-coverage
to our list of karma plugins in our karma config file:
plugins: [
'karma-jspm',
'karma-mocha',
'karma-chai',
'karma-chai-sinon',
'karma-chrome-launcher',
'karma-coverage'
],
Follow this up by adding the coverage reporter to the array of reporters:
reporters: ['dots', 'coverage'],
Next, we’re going to need to add the coverage preprocessor. We can do this by referencing our compiled JS code like so:
preprocessors: {
'src/**/*.html': ['ng-html2js'],
'.tmp/scripts/**/!(*spec).js': ['coverage']
},
And last but by no means least, we need to configure our coverage reporter. Now for this example we are going to use istanbul JS. We need to require istanbul at the top of our karma config file and then use it in the following config:
coverageReporter: {
includeAllSources: true,
instrumenters: { istanbul: istanbul },
instrumenter: {
'**/*.js': 'istanbul'
},
instrumenterOptions: {
istanbul: {
includeUntested: true
}
},
reporters: [
// We'll create a remapped (to typescript) html report from this json report - James
{
type: 'json',
dir: './.tmp/coverage-reports',
subdir: '.'
},
]
},
Just to talk through this config a little. We are including the includeAllSources
flag to ensure that all out files are loaded into the reporter, not only the files that are being run by tests. This is important as we won’t have a fair reflection of our coverage unless all files are included.
We are also only going to output the JSON coverage report to a coverage-reports
directory in the .tmp
folder. You might want to generate other kinds of reports here but for now we are only going to generate a JSON report (which we can use later to map the compiled JS back to the TypeScript files - but I’ll get to that in a minute).
Now lets give this a test…. Cue the buzzer! Something’s not quite right here (and this took me a while to figure out). This is the message that karma is giving us:
Error: (SystemJS) Module http://localhost:9876/.tmp/scripts/index.js interpreted as global module format, but called System.register.
Error: Module http://localhost:9876/.tmp/scripts/index.js interpreted as global module format, but called System.register.
at eval (http://localhost:9876/.tmp/scripts/index.js:9:46)
Evaluating http://localhost:9876/.tmp/scripts/index.js
Error loading http://localhost:9876/.tmp/scripts/index.spec.js
After a bit of googling, I founf the issue to be that karma-jspm doesn’t really understand this compiled code and we can add a meta option directly into the karma-jspm config in the karma config file. Something like this:
jspm: {
loadFiles: [
'.tmp/scripts/**/*.spec.js'
],
meta: {
'.tmp/scripts/*': {
format: 'register'
}
},
serveFiles: [
'.tmp/scripts/**/*.js',
'src/**/*.ts'
]
}
One thing to note is that it’s key you don’t add any file extension to the location of the script in the .tmp
folder. It must read as the following '.tmp/scripts/*'
otherwise it’s not going to work for you.
After adding the following we get this lovely console output:
Chrome 53.0.2785 (Mac OS X 10.11.6): Executed 1 of 1 SUCCESS (0.006 secs / 0 secs)
As well as a rather nice JSON coverage report. (of which I’m not going to post the output here). This leads me right onto remapping!
Re-mapping typescript files
One thing that is going to be really useful is to map the coverage reports from the transpiled JS code to the original TypeScript files. We can do this using remap-istanbul!
Lets just quickly install that dependancy now..
npm install remap-istanbul --save-dev
It’s rediculously easy to use.. simply reference your generated JSON coverage report and define which type of report you would like to be created. For this tutorial we want to create a HTML report so that we have something nice and pretty to screenshot, we can to this by creating the following npm
script:
{
"test:coverage": "remap-istanbul --input ./.tmp/coverage-reports/coverage-final.json --output coverage --type html"
}
This creates a HTML code coverage report that is mapped back to the original TypeScript files, and by just running a python -m SimpleHTTPServer 8000
command to serve these static files on port 8000
we can view our report. It should look something like below:
And if we look in depth at the file..
Remember, you can download the source files if you wish.. just run the npm install
task and you should be good to run the npm test scripts, which you can find in the package.json
.
Conclusion
So to sum up, we have gone through and implemented TypeScript code coverage using JSPM, istanbul and karma-coverage. We then mapped those reports back to the original TypeScript files to make it easier to determine exactly which lines of code are tested and which are not. I hope this helps someone going forward and you have any questions or run into any issues. Please don’t hesitate to get in touch using the comments section below!