Setup TypeScript code coverage for Electron applications

Environment

  • Node.js v10.9.0

To receive code coverage based on TypeScript source code within an Electron environment, we need to do the following:

  1. Tell electron-mocha to use babel
  2. Register @babel/preset-typescript in babel to compile our code on-the-fly
  3. Register istanbul plugin in babel to instrument our compiled code
  4. Register a after hook in mocha to write out our coverage information (provided by istanbul)
  5. Run nyc to create a HTML report from our coverage information stored in .nyc_output

package.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"devDependencies": {
"@babel/core": "7.3.3",
"@babel/plugin-proposal-class-properties": "7.3.3",
"@babel/preset-env": "7.3.1",
"@babel/preset-typescript": "7.3.3",
"@babel/register": "7.0.0",
"@types/mocha": "5.2.6",
"babel-plugin-istanbul": "5.1.1",
"electron": "4.0.5",
"electron-mocha": "6.0.4",
"nyc": "13.3.0",
"typescript": "3.3.3"
},
"main": "dist/main.js",
"scripts": {
"coverage": "yarn test && nyc report",
"test": "electron-mocha --require ./babel-register.js src/**/*.test.main.ts"
},
"version": "0.0.0"
}

babel-register.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
require('@babel/register')({
cache: false,
extensions: ['.ts'],
plugins: [
'@babel/proposal-class-properties',
[
'istanbul',
{
exclude: ['**/*.test*.ts'],
},
],
],
presets: [
[
'@babel/preset-env',
{
targets: {
node: 'current',
},
},
],
'@babel/preset-typescript',
],
});
tsconfig.json
1
2
3
4
5
6
7
8
9
10
11
12
13
{
"compilerOptions": {
"module": "commonjs",
"moduleResolution": "node",
"outDir": "dist",
"rootDir": "src",
"target": "es5"
},
"exclude": [
"dist",
"node_modules"
]
}

.nycrc.json

1
2
3
4
5
6
7
8
{
"all": true,
"exclude": ["**/*.test*.ts"],
"extension": [".ts"],
"include": ["src/**/*.ts"],
"per-file": false,
"reporter": ["html"]
}

after.test.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import * as fs from 'fs-extra';
import * as path from 'path';

declare global {
namespace NodeJS {
interface Global {
__coverage__: {};
}
}
}

const writeCoverageReport = (coverage: Object) => {
const outputFile = path.resolve(process.cwd(), `.nyc_output/coverage.${process['type']}.json`);
fs.outputJsonSync(outputFile, coverage);
};

after(() => {
const coverageInfo = global.__coverage__;
if (coverageInfo) {
writeCoverageReport(coverageInfo);
}
});

Takeaways

  • nyc can combine multiple coverage files (like coverage.json) into one report
  • eletron-mocha can run tests in an Electron renderer process and in an Electron main process
  • To get full code coverage, there needs to be a test run with test files for the main process (*.test.main.ts) and a second run for tests from the renderer process (*.test.renderer.ts)
  • Electron provides a process.type (can be browser or renderer) to indicate in which process the code runs
  • To have a universal after hook for both test runs, the process.type can be used in the coverage output file name
  • istanbul needs to exclude the test files, otherwise it will report code coverage for the test files too