@typescript-eslint/rule-tester
ESLint ルールのテストユーティリティ
これは、ESLint の組み込み `RuleTester` のフォークであり、TypeScript ルールのテストのために、より優れた型と追加機能を提供します。
使用方法
型認識しないルールは、次のようにテストできます。
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 />;',
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: [
/* ... */
],
},
// 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,
},
],
},
],
});
型認識テスト
型認識ルールは、ほぼ同じ方法でテストできますが、ディスク上にいくつかのファイルを作成する必要があります。TypeScript の制限により、プロジェクトの初期化にはディスク上の物理ファイルが必要となるため、ファイルはディスク上に存在する必要があります。近くにある `fixture` フォルダーを作成し、3 つのファイルを含めることをお勧めします。
- `file.ts` - 空のファイルにする必要があります。
- `react.tsx` - 空のファイルにする必要があります。
- `tsconfig.json` - テストに使用する設定です。例:
{
"compilerOptions": {
"strict": true
},
"include": ["file.ts", "react.tsx"]
}
`file.ts` と `react.tsx` の両方が空のファイルであることが重要です!ルールテスターはテストからの文字列コンテンツを自動的に使用します。空のファイルは初期化のためだけに存在します。
型認識設定を提供することで、ルールをテストできます。
const ruleTester = new RuleTester({
parserOptions: {
tsconfigRootDir: './path/to/your/folder/fixture',
project: './tsconfig.json',
},
});
この設定により、パーサーは型認識モードで自動的に実行され、以前と同じようにテストを作成できます。
テスト依存関係の制約
後方互換性と前方互換性を確保するために、依存関係の複数のバージョンに対してルールをテストすることが望ましい場合があります。後方互換性テストでは、一部のテストが古いバージョンの依存関係と互換性がないという複雑さが生じることがあります。たとえば、古いバージョンの TypeScript に対してテストしている場合、特定の機能によってパーサーエラーが発生する可能性があります!
// `Options` and `RangeOptions` are defined in the 'semver' package.
// We redeclare them here to avoid a peer dependency on that package:
export interface RangeOptions {
includePrerelease?: boolean | undefined;
loose?: boolean | undefined;
}
export interface SemverVersionConstraint {
readonly range: string;
readonly options?: RangeOptions | boolean;
}
export type AtLeastVersionConstraint =
| `${number}.${number}.${number}-${string}`
| `${number}.${number}.${number}`
| `${number}.${number}`
| `${number}`;
export type VersionConstraint =
| AtLeastVersionConstraint
| SemverVersionConstraint;
/**
* Passing a string for the value is shorthand for a '>=' constraint
*/
export type DependencyConstraint = Readonly<Record<string, VersionConstraint>>;
`RuleTester` を使用すると、個々のテストレベルまたはコンストラクターレベルで依存関係の制約を適用できます。
const ruleTester = new RuleTester({
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',
},
},
});
ruleTester.run('my-rule', rule, {
valid: [
{
code: 'const y = 2;',
dependencyConstraints: {
// this test won't run unless BOTH dependencies match the given ranges
first: '1.2.3',
second: '3.2.1',
},
},
],
invalid: [
/* ... */
],
});
`dependencyConstraints` オブジェクトに提供されたすべての依存関係は、テストがスキップされないように、指定された範囲と一致する必要があります。
特定のフレームワークを使用
ESLint の `RuleTester` は、テストのグローバルフックに依存しています。グローバルに使用できない場合、テストは次のようなエラーで失敗します。
Error: Missing definition for `afterAll` - you must set one using `RuleTester.afterAll` or there must be one defined globally as `afterAll`.
最初に `new RuleTester(...)` を呼び出す前に、`RuleTester` の静的プロパティを設定してください。
Mocha
`mochaGlobalSetup` フィクスチャで `RuleTester` の静的プロパティを設定することを検討してください。
import * as mocha from 'mocha';
import { RuleTester } from '@typescript-eslint/rule-tester';
RuleTester.afterAll = mocha.after;
Vitest
`setupFiles` スクリプトで `RuleTester` の静的プロパティを設定することを検討してください。
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.dokyumento.jp/config/#globals):
RuleTester.it = vitest.it;
RuleTester.itOnly = vitest.it.only;
RuleTester.describe = vitest.describe;
Node組み込みテストランナー
`--import`または`--require`フラグを使用して、プリロードモジュールで `RuleTester` の静的プロパティを設定することを検討してください。
// setup.js
import * as test from 'node:test';
import { RuleTester } from '@typescript-eslint/rule-tester';
RuleTester.afterAll = test.afterAll;
RuleTester.describe = test.describe;
RuleTester.it = test.it;
RuleTester.itOnly = test.it.only;
テストは、次のようにコマンドラインから実行できます。
node --import setup.js --test
オプション
`RuleTester` コンストラクターオプション
import type {
ClassicConfig,
ParserOptions,
} from '@typescript-eslint/utils/ts-eslint';
import type { DependencyConstraint } from './DependencyConstraint';
export interface RuleTesterConfig extends ClassicConfig.Config {
/**
* The default parser to use for tests.
* @default '@typescript-eslint/parser'
*/
readonly parser: string;
/**
* The default parser options to use for tests.
*/
readonly parserOptions?: Readonly<ParserOptions>;
/**
* Constraints that must pass in the current environment for any tests to run.
*/
readonly dependencyConstraints?: DependencyConstraint;
/**
* The default filenames to use for type-aware tests.
* @default { ts: 'file.ts', tsx: 'react.tsx' }
*/
readonly defaultFilenames?: Readonly<{
ts: string;
tsx: string;
}>;
}
有効なテストケースオプション
import type {
Linter,
ParserOptions,
SharedConfigurationSettings,
} from '@typescript-eslint/utils/ts-eslint';
import type { DependencyConstraint } from './DependencyConstraint';
export interface ValidTestCase<Options extends Readonly<unknown[]>> {
/**
* Name for the test case.
*/
readonly name?: string;
/**
* Code for the test case.
*/
readonly code: string;
/**
* Environments for the test case.
*/
readonly env?: Readonly<Linter.EnvironmentConfig>;
/**
* The fake filename for the test case. Useful for rules that make assertion about filenames.
*/
readonly filename?: string;
/**
* The additional global variables.
*/
readonly globals?: Readonly<Linter.GlobalsConfig>;
/**
* Options for the test case.
*/
readonly options?: Readonly<Options>;
/**
* The absolute path for the parser.
*/
readonly parser?: string;
/**
* Options for the parser.
*/
readonly parserOptions?: Readonly<ParserOptions>;
/**
* Settings for the test case.
*/
readonly settings?: Readonly<SharedConfigurationSettings>;
/**
* Run this case exclusively for debugging in supported test frameworks.
*/
readonly only?: boolean;
/**
* Skip this case in supported test frameworks.
*/
readonly skip?: boolean;
/**
* Constraints that must pass in the current environment for the test to run
*/
readonly dependencyConstraints?: DependencyConstraint;
}
無効なテストケースオプション
import type { AST_NODE_TYPES, AST_TOKEN_TYPES } from '@typescript-eslint/utils';
import type { ReportDescriptorMessageData } from '@typescript-eslint/utils/ts-eslint';
import type { DependencyConstraint } from './DependencyConstraint';
import type { ValidTestCase } from './ValidTestCase';
export interface SuggestionOutput<MessageIds extends string> {
/**
* Reported message ID.
*/
readonly messageId: MessageIds;
/**
* The data used to fill the message template.
*/
readonly data?: ReportDescriptorMessageData;
/**
* NOTE: Suggestions will be applied as a stand-alone change, without triggering multi-pass fixes.
* Each individual error has its own suggestion, so you have to show the correct, _isolated_ output for each suggestion.
*/
readonly output: string;
// we disallow this because it's much better to use messageIds for reusable errors that are easily testable
// readonly desc?: string;
}
export interface TestCaseError<MessageIds extends string> {
/**
* The 1-based column number of the reported start location.
*/
readonly column?: number;
/**
* The data used to fill the message template.
*/
readonly data?: ReportDescriptorMessageData;
/**
* The 1-based column number of the reported end location.
*/
readonly endColumn?: number;
/**
* The 1-based line number of the reported end location.
*/
readonly endLine?: number;
/**
* The 1-based line number of the reported start location.
*/
readonly line?: number;
/**
* Reported message ID.
*/
readonly messageId: MessageIds;
/**
* Reported suggestions.
*/
readonly suggestions?: readonly SuggestionOutput<MessageIds>[] | null;
/**
* The type of the reported AST node.
*/
readonly type?: AST_NODE_TYPES | AST_TOKEN_TYPES;
// we disallow this because it's much better to use messageIds for reusable errors that are easily testable
// readonly message?: string | RegExp;
}
export interface InvalidTestCase<
MessageIds extends string,
Options extends Readonly<unknown[]>,
> extends ValidTestCase<Options> {
/**
* Expected errors.
*/
readonly errors: readonly TestCaseError<MessageIds>[];
/**
* The expected code after autofixes are applied. If set to `null`, the test runner will assert that no autofix is suggested.
*/
readonly output?: string | null;
/**
* Constraints that must pass in the current environment for the test to run
*/
readonly dependencyConstraints?: DependencyConstraint;
}