Disable mutants
During mutation testing, you might run into equivalent mutants or simply mutants that you are not interested in.
An example​
Given this code:
function max(a, b) {
return a < b ? b : a;
}
And these tests:
describe('math', () => {
it('should return 4 for max(4, 3)', () => {
expect(max(4, 3)).eq(4);
});
it('should return 4 for max(3, 4)', () => {
expect(max(3, 4)).eq(4);
});
});
Stryker will generate (amongst others) these mutants:
function max(a, b) {
- return a < b ? b : a;
+ return true ? b : a; // 👽 1
+ return false ? b : a; // 👽 2
+ return a <= b ? b : a; // 👽 3
}
Mutant 1 and 2 are killed by the tests. However, mutant 3 isn't killed. In fact, mutant 3 cannot be killed because the mutated code is equivalent to the original. It is therefore called equivalent mutant.
Disable mutants​
StrykerJS supports three ways to ignore mutants.
- Exclude the mutator.
If you are not interested in a specific mutator. - Using a
// Stryker disable
comment.
Suitable for the one-off ignoring of mutants. - Using an ignore-plugin.
If you want to ignore the same pattern multiple times (and in multiple files).
Disabled mutants will remain in your report but will get the ignored
status. Mutants with this status in your report don't influence your mutation score but are still visible if you want to look for them.
Exclude the mutator​
You can turn off a mutator entirely. Do this by stating the mutator name in the mutator.excludedMutations
array in your Stryker configuration file:
{
"mutator": {
"excludedMutations": ["EqualityOperator"]
}
}
You can look up the mutator name inside your clear-text- or HTML report. For example, using the clear-text reporter (enabled by default), you can find the mutator name in your console:
#3. [Survived] EqualityOperator
src/math.js:3:12
- return a < b ? b : a;
+ return a <= b ? b : a;
Tests ran:
math should return 4 for max(4, 3)
math should return 4 for max(3, 4)
In the HTML report, you must select the mutant you want to ignore; the drawer at the bottom has the mutator name in its title.
However, excluding a mutator for all your files is a shotgun approach; it works but is now also disabled for other files and places. You may want to use a comment instead.
Using a // Stryker disable
comment.​
Available since Stryker 5.4
You can disable Stryker for a specific line of code using a comment.
function max(a, b) {
// Stryker disable next-line all
return a < b ? b : a;
}
After rerunning Stryker, the report looks like this:
It works but is different from what we want. As you can see, all mutants on line 4 are now disabled.
We can do better by naming the mutator we want to ignore:
function max(a, b) {
// Stryker disable next-line EqualityOperator
return a < b ? b : a;
}
We can even provide a custom reason for disabling this mutant behind a colon (:
). Disable reasons will also end up in your report (drawer below).
function max(a, b) {
// Stryker disable next-line EqualityOperator: The <= mutant results in an equivalent mutant
return a < b ? b : a;
}
After rerunning Stryker, the report looks like this:
Disable comment syntax​
Available since Stryker 5.4
The disabled comment is pretty powerful. Some more examples:
Disable an entire file:
// Stryker disable all
function max(a, b) {
return a < b ? b : a;
}
Disable parts of a file:
// Stryker disable all
function max(a, b) {
return a < b ? b : a;
}
// Stryker restore all
function min(a, b) {
return a < b ? b : a;
}
Disable 2 mutators for an entire file with a custom reason:
// Stryker disable EqualityOperator,ObjectLiteral: We'll implement tests for these next sprint
function max(a, b) {
return a < b ? b : a;
}
Turn off all mutators for an entire file, but restore the EqualityOperator
for 1 line:
// Stryker disable all
function max(a, b) {
// Stryker restore next-line EqualityOperator
return a < b ? b : a;
}
The syntax looks like this:
// Stryker [disable|restore] [next-line] *mutatorList*[: custom reason]
The comment always starts with // Stryker
, followed by either disable
or restore
. Next, you can specify whether this comment targets the next-line
or all lines from this point on (by not using next-line
). As for the mutator list, this is either a comma-separated list of mutators or all
to signal this comment targets all mutators. Last is an optional custom reason text behind the :
colon.
Using an ignore-plugin​
Available since Stryker 7.3
You might not be interested in testing specific code patterns in some projects. You can use // Stryker disable
comments for these. However, this gets tedious quickly.
For example:
function add(a, b) {
console.debug('add called');
return a + b;
}
function subtract(a, b) {
console.debug('subtract called');
return a + b;
}
In the code above, you might want to ignore all mutants in console.debug
statements in this code. This is where an ignore-plugin can help.
To declare an ignore-plugin, first install @stryker-mutator/api
as a dev dependency:
npm i -D @stryker-mutator/api
Now add a "stryker-console-ignorer.js" to your project:
// stryker-console-ignorer.js
import { PluginKind, declareValuePlugin } from '@stryker-mutator/api/plugin';
export const strykerPlugins = [declareValuePlugin(PluginKind.Ignore, 'console.debug', {
shouldIgnore(path) {
// Define the conditions for which you want to ignore mutants
if (
path.isExpressionStatement() &&
path.node.expression.type === 'CallExpression' &&
path.node.expression.callee.type === 'MemberExpression' &&
path.node.expression.callee.object.type === 'Identifier' &&
path.node.expression.callee.object.name === 'console' &&
path.node.expression.callee.property.type === 'Identifier' &&
path.node.expression.callee.property.name === 'debug'
) {
// Return the ignore reason
return "We're not interested in testing `console.debug` statements, see ADR 648.";
}
}
})];
In the above example, you declare an ignore-plugin with the name 'console.debug'
and an Ignorer
. An Ignorer
is an object with a shouldIgnore(path)
method. Stryker will execute this method on each node of the abstract syntax tree (AST). To ignore mutants in the current node and child nodes, return a non-empty ignore reason as a string here. The path
parameter is a babel NodePath
object, see 'visiting' in the babel handbook for more information on the NodePath
API.
You configure this plugin in your 'stryker.config.json' file:
{
"ignorers": ["console.debug"],
"plugins": ["@stryker-mutator/*", "./stryker-console-ignorer.js"]
}
After rerunning Stryker, your report will look like this.
If you want TypeScript type-safety on the path
being passed into your ignore-plugin, you will need to install the babel types yourself: npm i -D @types/babel__core
and add this TypeScript file somewhere in your project:
/// <reference types="@stryker-mutator/api/ignore" />
import type babel from '@babel/core';
declare module '@stryker-mutator/api/ignore' {
export interface NodePath extends babel.NodePath {}
}
If you want to write the plugin itself as TypeScript as well, you will need to transpile it to JavaScript yourself. Either by doing it before you run Stryker, or using a just-in-time compiler like ts-node
.