|
| 1 | +--- |
| 2 | +id: rule-tester |
| 3 | +sidebar_label: rule-tester |
| 4 | +--- |
| 5 | + |
| 6 | +import CodeBlock from '@theme/CodeBlock'; |
| 7 | + |
| 8 | +# `@typescript-eslint/rule-tester` |
| 9 | + |
| 10 | +> A utility for testing ESLint rules |
| 11 | +
|
| 12 | +## Usage |
| 13 | + |
| 14 | +For non-type-aware rules you can test them as follows: |
| 15 | + |
| 16 | +```ts |
| 17 | +import { RuleTester } from '@typescript-eslint/rule-tester'; |
| 18 | +import rule from '../src/rules/my-rule.ts'; |
| 19 | + |
| 20 | +const ruleTester = new RuleTester({ |
| 21 | + parser: '@typescript-eslint/parser', |
| 22 | +}); |
| 23 | + |
| 24 | +ruleTester.run('my-rule', rule, { |
| 25 | + valid: [ |
| 26 | + // valid tests can be a raw string, |
| 27 | + 'const x = 1;', |
| 28 | + // or they can be an object |
| 29 | + { |
| 30 | + code: 'const y = 2;', |
| 31 | + options: [{ ruleOption: true }], |
| 32 | + }, |
| 33 | + |
| 34 | + // you can enable JSX parsing by passing parserOptions.ecmaFeatures.jsx = true |
| 35 | + { |
| 36 | + code: 'const z = <div />;', |
| 37 | + parserOptions: { |
| 38 | + ecmaFeatures: { |
| 39 | + jsx: true, |
| 40 | + }, |
| 41 | + }, |
| 42 | + }, |
| 43 | + ], |
| 44 | + invalid: [ |
| 45 | + // invalid tests must always be an object |
| 46 | + { |
| 47 | + code: 'const a = 1;', |
| 48 | + // invalid tests must always specify the expected errors |
| 49 | + errors: [ |
| 50 | + { |
| 51 | + messageId: 'ruleMessage', |
| 52 | + // If applicable - it's recommended that you also assert the data in |
| 53 | + // addition to the messageId so that you can ensure the correct message |
| 54 | + // is generated |
| 55 | + data: { |
| 56 | + placeholder1: 'a', |
| 57 | + }, |
| 58 | + }, |
| 59 | + ], |
| 60 | + }, |
| 61 | + |
| 62 | + // fixers can be tested using the output parameter |
| 63 | + { |
| 64 | + code: 'const b = 1;', |
| 65 | + output: 'const c = 1;', |
| 66 | + errors: [ |
| 67 | + /* ... */ |
| 68 | + ], |
| 69 | + }, |
| 70 | + // passing `output = null` will enforce the code is NOT changed |
| 71 | + { |
| 72 | + code: 'const c = 1;', |
| 73 | + output: null, |
| 74 | + errors: [ |
| 75 | + /* ... */ |
| 76 | + ], |
| 77 | + }, |
| 78 | + |
| 79 | + // suggestions can be tested via errors |
| 80 | + { |
| 81 | + code: 'const d = 1;', |
| 82 | + output: null, |
| 83 | + errors: [ |
| 84 | + { |
| 85 | + messageId: 'suggestionError', |
| 86 | + suggestions: [ |
| 87 | + { |
| 88 | + messageId: 'suggestionOne', |
| 89 | + output: 'const e = 1;', |
| 90 | + }, |
| 91 | + ], |
| 92 | + }, |
| 93 | + ], |
| 94 | + }, |
| 95 | + // passing `suggestions = null` will enforce there are NO suggestions |
| 96 | + { |
| 97 | + code: 'const d = 1;', |
| 98 | + output: null, |
| 99 | + errors: [ |
| 100 | + { |
| 101 | + messageId: 'noSuggestionError', |
| 102 | + suggestions: null, |
| 103 | + }, |
| 104 | + ], |
| 105 | + }, |
| 106 | + ], |
| 107 | +}); |
| 108 | +``` |
| 109 | + |
| 110 | +### Type-Aware Testing |
| 111 | + |
| 112 | +Type-aware rules can be tested in almost exactly the same way, except you need to create some files on disk. |
| 113 | +We require files on disk due to a limitation with TypeScript in that it requires physical files on disk to initialize the project. |
| 114 | +We suggest creating a `fixture` folder nearby that contains three files: |
| 115 | + |
| 116 | +1. `file.ts` - this should be an empty file. |
| 117 | +2. `react.tsx` - this should be an empty file. |
| 118 | +3. `tsconfig.json` - this should be the config to use for your test, for example: |
| 119 | + ```json |
| 120 | + { |
| 121 | + "compilerOptions": { |
| 122 | + "strict": true |
| 123 | + }, |
| 124 | + "include": ["file.ts", "react.tsx"] |
| 125 | + } |
| 126 | + ``` |
| 127 | + |
| 128 | +:::caution |
| 129 | +It's important to note that both `file.ts` and `react.tsx` must both be empty files! |
| 130 | +The rule tester will automatically use the string content from your tests - the empty files are just there for initialization. |
| 131 | +::: |
| 132 | + |
| 133 | +You can then test your rule by providing the type-aware config: |
| 134 | + |
| 135 | +```ts |
| 136 | +const ruleTester = new RuleTester({ |
| 137 | + parser: '@typescript-eslint/parser', |
| 138 | + // Added lines start |
| 139 | + parserOptions: { |
| 140 | + tsconfigRootDir: './path/to/your/folder/fixture', |
| 141 | + project: './tsconfig.json', |
| 142 | + }, |
| 143 | + // Added lines end |
| 144 | +}); |
| 145 | +``` |
| 146 | + |
| 147 | +With that config the parser will automatically run in type-aware mode and you can write tests just like before. |
| 148 | + |
| 149 | +### Test Dependency Constraints |
| 150 | + |
| 151 | +Sometimes it's desirable to test your rule against multiple versions of a dependency to ensure backwards and forwards compatibility. |
| 152 | +With backwards-compatibility testing there comes a complication in that some tests may not be compatible with an older version of a dependency. |
| 153 | +For example - if you're testing against an older version of TypeScript, certain features might cause a parser error! |
| 154 | + |
| 155 | +import DependencyConstraint from '!!raw-loader!../../packages/rule-tester/src/types/DependencyConstraint.ts'; |
| 156 | + |
| 157 | +<CodeBlock language="ts">{DependencyConstraint}</CodeBlock> |
| 158 | + |
| 159 | +The `RuleTester` allows you to apply dependency constraints at either an individual test or constructor level. |
| 160 | + |
| 161 | +```ts |
| 162 | +const ruleTester = new RuleTester({ |
| 163 | + parser: '@typescript-eslint/parser', |
| 164 | + // Added lines start |
| 165 | + dependencyConstraints: { |
| 166 | + // none of the tests will run unless `my-dependency` matches the semver range `>=1.2.3` |
| 167 | + 'my-dependency': '1.2.3', |
| 168 | + // you can also provide granular semver ranges |
| 169 | + 'my-granular-dep': { |
| 170 | + // none of the tests will run unless `my-granular-dep` matches the semver range `~3.2.1` |
| 171 | + range: '~3.2.1', |
| 172 | + }, |
| 173 | + }, |
| 174 | + // Added lines end |
| 175 | +}); |
| 176 | + |
| 177 | +ruleTester.run('my-rule', rule, { |
| 178 | + valid: [ |
| 179 | + { |
| 180 | + code: 'const y = 2;', |
| 181 | + // Added lines start |
| 182 | + dependencyConstraints: { |
| 183 | + // this test won't run unless BOTH dependencies match the given ranges |
| 184 | + first: '1.2.3', |
| 185 | + second: '3.2.1', |
| 186 | + }, |
| 187 | + // Added lines end |
| 188 | + }, |
| 189 | + ], |
| 190 | + invalid: [ |
| 191 | + /* ... */ |
| 192 | + ], |
| 193 | +}); |
| 194 | +``` |
| 195 | + |
| 196 | +All dependencies provided in the `dependencyConstraints` object must match their given ranges in order for a test to not be skipped. |
| 197 | + |
| 198 | +## Options |
| 199 | + |
| 200 | +### `RuleTester` constructor options |
| 201 | + |
| 202 | +import RuleTesterConfig from '!!raw-loader!../../packages/rule-tester/src/types/RuleTesterConfig.ts'; |
| 203 | + |
| 204 | +<CodeBlock language="ts">{RuleTesterConfig}</CodeBlock> |
| 205 | + |
| 206 | +### Valid test case options |
| 207 | + |
| 208 | +import ValidTestCase from '!!raw-loader!../../packages/rule-tester/src/types/ValidTestCase.ts'; |
| 209 | + |
| 210 | +<CodeBlock language="ts">{ValidTestCase}</CodeBlock> |
| 211 | + |
| 212 | +### Invalid test case options |
| 213 | + |
| 214 | +import InvalidTestCase from '!!raw-loader!../../packages/rule-tester/src/types/InvalidTestCase.ts'; |
| 215 | + |
| 216 | +<CodeBlock language="ts">{InvalidTestCase}</CodeBlock> |
0 commit comments