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

babel-register.js

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

.nycrc.json

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

after.test.ts

1
import * as fs from 'fs-extra';
2
import * as path from 'path';
3
4
declare global {
5
  namespace NodeJS {
6
    interface Global {
7
      __coverage__: {};
8
    }
9
  }
10
}
11
12
const writeCoverageReport = (coverage: Object) => {
13
  const outputFile = path.resolve(process.cwd(), `.nyc_output/coverage.${process['type']}.json`);
14
  fs.outputJsonSync(outputFile, coverage);
15
};
16
17
after(() => {
18
  const coverageInfo = global.__coverage__;
19
  if (coverageInfo) {
20
    writeCoverageReport(coverageInfo);
21
  }
22
});

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