Code coverage using Istanbul for Ember apps.
- If using Mocha, Testem
>= 1.6.0
for which you need ember-cli> 2.4.3
- If using Mirage you need
ember-cli-mirage >= 0.1.13
- If using Pretender (even as a dependency of Mirage) you need
pretender >= 0.11.0
- If using Mirage or Pretender, you need to set up a passthrough for coverage to be written.
ember-cli-babel >= 6.0.0
ember install ember-cli-code-coverage
In order to gather code coverage information, you must first install the Babel plugins in each project that you'd like to have instrumented.
For classic apps (ember-cli-build.js):
let app = new EmberApp(defaults, {
babel: {
plugins: [...require('ember-cli-code-coverage').buildBabelPlugin()],
},
});
For embroider apps (ember-cli-build.js):
let app = new EmberApp(defaults, {
babel: {
plugins: [...require('ember-cli-code-coverage').buildBabelPlugin({ embroider: true })],
},
});
For in-repo and standalone addons (index.js):
module.exports = {
name: require('./package').name,
options: {
babel: {
plugins: [...require('ember-cli-code-coverage').buildBabelPlugin()],
},
},
};
For in-repo engines (index.js):
module.exports = EngineAddon.extend({
// ...
included() {
this._super.included.apply(this, arguments);
this.options.babel.plugins.push(...require('ember-cli-code-coverage').buildBabelPlugin());
},
});
For app
files in standalone addons (ember-cli-build.js):
let app = new EmberAddon(defaults, {
babel: {
plugins: [...require('ember-cli-code-coverage').buildBabelPlugin()]
},
});
tests/test-helpers.js:
import { forceModulesToBeLoaded, sendCoverage } from 'ember-cli-code-coverage/test-support';
import Qunit from 'qunit';
QUnit.done(async function() {
forceModulesToBeLoaded();
await sendCoverage();
});
Coverage will only be generated when an environment variable is true (by default COVERAGE
) and running your test command like normal.
For example:
COVERAGE=true ember test
If you want your coverage to work on both Unix and Windows, you can do this:
npm install cross-env --save-dev
and then:
cross-env COVERAGE=true ember test
When running with parallel
set to true, the final reports can be merged by using ember coverage-merge
. The final merged output will be stored in the coverageFolder
.
If you intend to use ember test
with the --path
flag, you should generate the build
with coverageEnvVar
set as true. This is because the code is instrumented for
coverage during the build.
For example:
COVERAGE=true ember build --environment=test --output-path=dist
followed by
COVERAGE=true ember test --path=dist
Steps:
- in
tsconfig.json
{
"compilerOptions": {
"inlineSourceMap": true,
"inlineSources": true
}
}
- in
ember-cli-build.js
const app = new EmberApp(defaults, {
babel: {
sourceMaps: 'inline'
},
sourcemaps: {
enabled: true,
extensions: ['js']
}
});
- in
package.json
specify latest available version
{
devDependencies: {
"ember-cli-code-coverage": "^2.1.0",
}
}
- in `ember-cli-build.js
const app = new EmberApp(defaults, {
'ember-template-imports': {
inline_source_map: true,
},
});
Configuration is optional. It should be put in a file at config/coverage.js
(configPath
configuration in package.json is honored). In addition to this you can configure Istanbul by adding a .istanbul.yml
file to the root directory of your app (See https://github.com/gotwarlost/istanbul#configuring)
-
coverageEnvVar
: Defaults toCOVERAGE
. This is the environment variable that when set will cause coverage metrics to be generated. -
reporters
: Defaults to['lcov', 'html']
. Thejson-summary
reporter will be added to anything set here, it is required. This can be any reporters supported by Istanbul. -
excludes
: Defaults to['*/mirage/**/*']
. An array of globs to exclude from instrumentation. Useful to exclude files from coverage statistics. -
extension
: Defaults to['.gjs', '.gts', '.js', '.ts', '.cjs', '.mjs', '.mts', '.cts']
. Tell Istanbul to instrument only files with the provided extensions. -
coverageFolder
: Defaults tocoverage
. A folder relative to the root of your project to store coverage results. -
parallel
: Defaults tofalse
. Should be set to true if parallel testing is being used for separate test runs, for example when using ember-exam with the--partition
flag. This will generate the coverage reports in directories suffixed with_<random_string>
to avoid overwriting other threads reports. These reports can be joined by using theember coverage-merge
command (potentially as part of the posttest hook in yourpackage.json
). -
modifyAssetLocation
: Optional function that will allow you to override where a file actually lives inside of your project. See Advanced customization on how to use this function in practice.
module.exports = {
coverageEnvVar: 'COV'
}
To work, this addon has to post coverage results back to a middleware at /write-coverage
.
If you are using ember-cli-mirage
you should add the following:
// in mirage/config.js
this.passthrough('/write-coverage');
this.namespace = 'api'; // It's important that the passthrough for coverage is before the namespace, otherwise it will be prefixed.
If you are using ember-cli-pretender
you should add the following:
// where ever you set up the Pretender Server
var server = new Pretender(function () {
this.post('/write-coverage', this.passthrough);
});
The forceModulesToBeLoaded
function can potentially cause unintended side effects when executed. You can pass custom filter fuctions that allow
you to specify which modules will be force loaded or not:
QUnit.done(async () => {
// type will be either webpack and/or require
forceModulesToBeLoaded((type, moduleName) => { return true; });
await sendCoverage();
});
Under the hood, ember-cli-code-coverage
attempts to "de-namespacify" paths into their real on disk location inside of
project.root
(ie give a namespaced path like lib/inrepo/components/foo.js would live in lib/inrepo/addon/components/foo.js). It makes
some assumptions (where files live in in-repo addons vs app code for example) and sometimes those assumptions might not hold. Passing a
function modifyAssetLocation
in your configuration file will allow you to override where a file actually lives inside
of your project. The returned string should be relative to your project root.
module.exports = {
modifyAssetLocation(root, relativePath) {
let appPath = relativePath.replace('my-project-name', 'app');
// here is an example of saying that `app/components/foo.js` actually
// lives in `lib/inrepo/app/components/foo.js` on disk.
if (fs.existsSync(path.join(root, 'lib', 'inrepo', appPath))) {
return path.join('lib', 'inrepo', appPath);
}
return false;
},
};
This addon was inspired by ember-cli-blanket
.
The primary differences are that this addon uses Istanbul rather than Blanket for coverage and it instruments your application code as part of the build, when enabled.