stryker-mutator / stryker-js

Mutation testing for JavaScript and friends
https://stryker-mutator.io
Apache License 2.0
2.55k stars 242 forks source link

Stryker.js mutates the React Hooks dependencies array. #4743

Closed guillermosnipe closed 4 months ago

guillermosnipe commented 4 months ago

Summary

When mutating React + Typescript code the ArrayDeclaration mutator mutates the react hooks dep array.

Expected result: Stryker doesn't mutate react hooks dependencies array.

Stryker config

Not Applicable

Test runner config

Not Applicable

Stryker environment

Not Applicable

+-- @stryker-mutator/core@8.2.3
+-- @stryker-mutator/jest-runner@8.2.3
nicojs commented 4 months ago

Hi @guillermosnipe 🙋‍♂️ thanks for opening this issue.

This use case is precisely why we've created the ignore plugin feature. You can configure it like this:

stryker.conf.json

{
  "ignorers": ["react-ignorer"],
  "plugins": [
    "@stryker-mutator/*",
    "./react-ignorer.mjs"
  ]
}

react-ignorer.mjs

import { PluginKind, declareValuePlugin } from '@stryker-mutator/api/plugin';

export const strykerPlugins = [
  declareValuePlugin(PluginKind.Ignore, 'react-ignorer', {
    /**
     * @param {import('@babel/core').NodePath} path
     */
    shouldIgnore(path) {
      // Define the conditions for which you want to ignore mutants
      if (
        path.isArrayExpression() &&
        path.parentPath.isCallExpression() &&
        path.parentPath.node.callee.type === 'Identifier' &&
        path.parentPath.node.callee.name === 'useEffect' &&
        path.parentPath.node.arguments[1] === path.node
      ) {
        return "Don't mutate useEffect dependencies";
      }
    },
  }),
];

See this example project: https://github.com/nicojs/example-react-ignorer-plugin

guillermosnipe commented 4 months ago

That's amazing! Thanks a lot :)

guillermosnipe commented 4 months ago

Hi @nicojs, thanks for the script, it worked like a charm.

I made some minor adjustments, and I wanted to share it back here in case someone finds it useful.

import { PluginKind, declareValuePlugin } from '@stryker-mutator/api/plugin';

export const strykerPlugins = [
  declareValuePlugin(PluginKind.Ignore, 'react-ignorer', {
    /**
     * @param {import('@babel/core').NodePath} path
     */
    shouldIgnore(path) {

      const hookNameRegex = /use[A-Z][a-zA-Z]*/;

      // Define the conditions for which you want to ignore mutants
      if (
        path.isArrayExpression() &&
        path.parentPath.isCallExpression() &&
        path.parentPath.node.callee.type === 'Identifier' &&
        hookNameRegex.test(path.parentPath.node.callee.name) &&
        path.parentPath.node.arguments[1] === path.node
      ) {
        return "Don't mutate react hooks dependencies array";
      }
    },
  }),
];