id | sidebar_label |
---|---|
rule-tester |
rule-tester |
import CodeBlock from '@theme/CodeBlock';
A utility for testing ESLint rules
This is a fork of ESLint's built-in RuleTester
to provide some better types and additional features for testing TypeScript rules.
For non-type-aware rules you can test them as follows:
import { RuleTester } from '@typescript-eslint/rule-tester';
import rule from '../src/rules/my-rule.ts';
const ruleTester = new RuleTester();
ruleTester.run('my-rule', rule, {
valid: [
// valid tests can be a raw string,
'const x = 1;',
// or they can be an object
{
code: 'const y = 2;',
options: [{ ruleOption: true }],
},
// you can enable JSX parsing by passing parserOptions.ecmaFeatures.jsx = true
{
code: 'const z = <div />;',
languageOptions: {
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
},
},
],
invalid: [
// invalid tests must always be an object
{
code: 'const a = 1;',
// invalid tests must always specify the expected errors
errors: [
{
messageId: 'ruleMessage',
// If applicable - it's recommended that you also assert the data in
// addition to the messageId so that you can ensure the correct message
// is generated
data: {
placeholder1: 'a',
},
},
],
},
// fixers can be tested using the output parameter
{
code: 'const b = 1;',
output: 'const c = 1;',
errors: [
/* ... */
],
},
// passing `output = null` will enforce the code is NOT changed
{
code: 'const c = 1;',
output: null,
errors: [
/* ... */
],
},
// Multi-pass fixes can be tested using the array form of output.
// Note: this is unique to typescript-eslint, and doesn't exist in ESLint core.
{
code: 'const d = 1;',
output: ['const e = 1;', 'const f = 1;'],
errors: [
/* ... */
],
},
// suggestions can be tested via errors
{
code: 'const d = 1;',
output: null,
errors: [
{
messageId: 'suggestionError',
suggestions: [
{
messageId: 'suggestionOne',
output: 'const e = 1;',
},
],
},
],
},
// passing `suggestions = null` will enforce there are NO suggestions
{
code: 'const d = 1;',
output: null,
errors: [
{
messageId: 'noSuggestionError',
suggestions: null,
},
],
},
],
});
Type-aware rules can be tested in almost exactly the same way as regular code, using parserOptions.projectService
.
Most rule tests specify parserOptions.allowDefaultProject: ["*.ts*"]
to enable type checking on all test files.
You can then test your rule by providing the type-aware config:
const ruleTester = new RuleTester({
// Added lines start
languageOptions: {
parserOptions: {
projectService: {
allowDefaultProject: ['*.ts*'],
},
tsconfigRootDir: './path/to/your/folder/fixture',
},
},
// Added lines end
});
With that config the parser will automatically run in type-aware mode and you can write tests just like before.
When not specified with a filename
option, RuleTester
uses the following test file names:
file.ts
: by defaultreact.tsx
: ifparserOptions.ecmaFeatures.jsx
is enabled
Sometimes it's desirable to test your rule against multiple versions of a dependency to ensure backwards and forwards compatibility. With backwards-compatibility testing there comes a complication in that some tests may not be compatible with an older version of a dependency. For example - if you're testing against an older version of TypeScript, certain features might cause a parser error!
import DependencyConstraint from '!!raw-loader!../../packages/rule-tester/src/types/DependencyConstraint.ts';
{DependencyConstraint}
The RuleTester
allows you to apply dependency constraints at either an individual test or constructor level.
const ruleTester = new RuleTester({
// Added lines start
dependencyConstraints: {
// none of the tests will run unless `my-dependency` matches the semver range `>=1.2.3`
'my-dependency': '1.2.3',
// you can also provide granular semver ranges
'my-granular-dep': {
// none of the tests will run unless `my-granular-dep` matches the semver range `~3.2.1`
range: '~3.2.1',
},
},
// Added lines end
});
ruleTester.run('my-rule', rule, {
valid: [
{
code: 'const y = 2;',
// Added lines start
dependencyConstraints: {
// this test won't run unless BOTH dependencies match the given ranges
first: '1.2.3',
second: '3.2.1',
},
// Added lines end
},
],
invalid: [
/* ... */
],
});
All dependencies provided in the dependencyConstraints
object must match their given ranges in order for a test to not be skipped.
ESLint's RuleTester
relies on some global hooks for tests.
If they aren't available globally, your tests will fail with an error like:
Error: Missing definition for `afterAll` - you must set one using `RuleTester.afterAll` or there must be one defined globally as `afterAll`.
:::tip
Be sure to set RuleTester
's static properties before calling new RuleTester(...)
for the first time.
:::
Consider setting up RuleTester
's static properties in a mochaGlobalSetup
fixture:
import * as mocha from 'mocha';
import { RuleTester } from '@typescript-eslint/rule-tester';
RuleTester.afterAll = mocha.after;
Consider setting up RuleTester
's static properties in a preloaded module using the --import
or --require
flag:
// setup.js
import * as test from 'node:test';
import { RuleTester } from '@typescript-eslint/rule-tester';
RuleTester.afterAll = test.after;
RuleTester.describe = test.describe;
RuleTester.it = test.it;
RuleTester.itOnly = test.it.only;
Tests can then be run from the command line like so:
node --import setup.js --test
Consider setting up RuleTester
's static properties in a setupFiles
script:
import * as vitest from 'vitest';
import { RuleTester } from '@typescript-eslint/rule-tester';
RuleTester.afterAll = vitest.afterAll;
// If you are not using vitest with globals: true (https://vitest.dev/config/#globals):
RuleTester.it = vitest.it;
RuleTester.itOnly = vitest.it.only;
RuleTester.describe = vitest.describe;
In general, RuleTester
can support any test framework that exposes hooks for running code before or after rules.
From RuleTester
's Static Properties, assign any of the following that the running test framework supports.
afterAll
describe
it
itOnly
I.e.:
import * as test from '...';
import { RuleTester } from '@typescript-eslint/rule-tester';
RuleTester.afterAll = test.after;
RuleTester.describe = test.describe;
RuleTester.it = test.it;
RuleTester.itOnly = test.it.only;
import RuleTesterConfig from '!!raw-loader!../../packages/rule-tester/src/types/RuleTesterConfig.ts';
{RuleTesterConfig}
import ValidTestCase from '!!raw-loader!../../packages/rule-tester/src/types/ValidTestCase.ts';
{ValidTestCase}
import InvalidTestCase from '!!raw-loader!../../packages/rule-tester/src/types/InvalidTestCase.ts';
{InvalidTestCase}
Each of the following properties may be assigned to as static members of the RuleTester
class.
For example, to assign afterAll
:
import { RuleTester } from '@typescript-eslint/rule-tester';
RuleTester.afterAll = () => {
// ...
};
Runs after all the tests in this file have completed.
Creates a test grouping.
Skips running the tests inside this describe
.
Creates a test closure.
Skips all other tests in the current file.
Skips running this test.