Safeguard
Developer Tools•

How do you test a custom ESLint rule with RuleTester?

Test a custom ESLint rule with RuleTester: write valid and invalid cases, assert on messageId, and add edge cases before you turn the rule on in CI.

Safeguard Security Team
4 min read

Test a custom ESLint rule with the built-in RuleTester class. Give it a rule and two lists: valid code that must produce no report, and invalid code with the errors you expect. Assert on messageId, not message text, and add edge cases for every false positive you have seen.

What RuleTester does

RuleTester is a utility that ESLint uses internally for its bundled rules, and plugins can use it too. Per the ESLint tutorial, it needs no third-party testing library, and it works with common runners. The run method throws an error if any scenario fails, and it requires at least one valid and one invalid scenario.

A basic test file

const { RuleTester } = require("eslint");
const rule = require("./no-nonliteral-shell");

const ruleTester = new RuleTester({
  languageOptions: { ecmaVersion: 2022, sourceType: "module" }
});

ruleTester.run("no-nonliteral-shell", rule, {
  valid: [
    'runShell("ls")',
    'other(userInput)'
  ],
  invalid: [
    {
      code: "runShell(userInput)",
      errors: [{ messageId: "nonLiteral" }]
    }
  ]
});

Without constructor options, RuleTester uses the ESLint defaults of ecmaVersion latest and sourceType module. Set the options explicitly if your code base needs something else.

What to put in each list

ListWhat belongs thereWhy
validThe safe pattern you want to allowProves the rule does not over-report
validNear misses, such as a different function with a similar nameCatches name matching that is too loose
invalidThe exact pattern you set out to catchProves the rule works at all
invalidVariants: template literals, concatenation, aliased callsShows which variants you cover on purpose
valid or invalidEvery real false positive reported by developersStops regressions

Steps to a test suite you can trust

  1. Write the invalid case first and watch it fail before the rule exists.
  2. Add the valid case that looks most like it.
  3. Assert on messageId so rewording a message does not break tests. The ESLint docs also show an assertion option that can require a message or message data, if you want stricter checks.
  4. Add a test for each bug report. Name the case after the ticket in a comment.
  5. Run the suite in CI so a rule change cannot merge with failing tests.

Test the options too

If your rule takes options, the schema is mandatory, so test that valid options pass and that a bad option fails loudly. A rule that silently ignores a typo in its config is worse than no rule, because it looks enabled.

What tests cannot tell you

Fixtures show that the rule does what you meant. They do not show how it behaves on your real code base. Before enabling a rule as an error, run it on the full repository in report only mode and read a sample of the results. A rule can pass every unit test and still be noisy on code shaped differently than your fixtures.

Try it yourself

Click through it at your own pace, or open it full screen.

Open a few findings and ask of each one: would my valid list have contained this code, or my invalid list?

Quick check

Should tests assert on message text?

Prefer messageId. Message text changes whenever someone improves the wording, and those edits should not break the suite.

How many test cases is enough?

There is no fixed number. Cover the intended pattern, the closest safe look-alikes, each variant you decided to support, and every false positive you have fixed.

Safeguard's scanning covers dependencies and SBOMs, and its reachability analysis helps show which findings your code can actually call. You can run a free scan on a repository.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.