Skip to main content

@typescript-eslint/rule-tester

npm: @typescript-eslint/rule-tester v8.27.0

用于测试 ESLint 规则的实用程序

¥A utility for testing ESLint rules

这是 ESLint 内置 RuleTester 的一个分支,用于为测试 TypeScript 规则提供一些更好的类型和附加功能。

¥This is a fork of ESLint's built-in RuleTester to provide some better types and additional features for testing TypeScript rules.

用法

¥Usage

对于非类型感知规则,你可以按如下方式测试它们:

¥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 Testing

使用 parserOptions.projectService,类型感知规则可以以与常规代码几乎完全相同的方式进行测试。大多数规则测试指定 parserOptions.allowDefaultProject: ["*.ts*"] 以在所有测试文件上启用类型检查。

¥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({
languageOptions: {
parserOptions: {
projectService: {
allowDefaultProject: ['*.ts*'],
},
tsconfigRootDir: './path/to/your/folder/fixture',
},
},
});

通过该配置,解析器将自动在类型感知模式下运行,你可以像以前一样编写测试。

¥With that config the parser will automatically run in type-aware mode and you can write tests just like before.

当没有使用 filename 选项指定时,RuleTester 使用以下测试文件名:

¥When not specified with a filename option, RuleTester uses the following test file names:

  • file.ts:默认情况下

    ¥file.ts: by default

  • react.tsx:如果启用 parserOptions.ecmaFeatures.jsx

    ¥react.tsx: if parserOptions.ecmaFeatures.jsx is enabled

测试依赖约束

¥Test Dependency Constraints

有时需要针对依赖的多个版本测试你的规则,以确保向后和向前兼容性。向后兼容性测试会带来一些复杂性,因为某些测试可能与旧版本的依赖不兼容。例如 - 如果你正在针对旧版本的 TypeScript 进行测试,某些功能可能会导致解析器错误!

¥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!

// `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 options?: boolean | RangeOptions;
readonly range: string;
}
export type AtLeastVersionConstraint =
| `${number}.${number}.${number}`
| `${number}.${number}.${number}-${string}`
| `${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 允许你在单个测试或构造函数级别应用依赖约束。

¥The RuleTester allows you to apply dependency constraints at either an individual test or constructor level.

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 对象中提供的所有依赖必须匹配其给定的范围,以免跳过测试。

¥All dependencies provided in the dependencyConstraints object must match their given ranges in order for a test to not be skipped.

具有特定框架

¥With Specific Frameworks

ESLint 的 RuleTester 依赖于一些全局钩子进行测试。如果它们在全局作用域内不可用,你的测试将失败并出现如下错误:

¥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`.
提示

请务必在第一次调用 new RuleTester(...) 之前设置 RuleTester 的静态属性。

¥Be sure to set RuleTester's static properties before calling new RuleTester(...) for the first time.

Mocha

考虑在 mochaGlobalSetup 夹具 中设置 RuleTester 的静态属性:

¥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;

Node.js (node:test)

考虑使用 --import--require 标志在预加载模块中设置 RuleTester 的静态属性:

¥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

Vitest

考虑在 setupFiles 脚本 中设置 RuleTester 的静态属性:

¥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;

其他框架

¥Other Frameworks

一般来说,RuleTester 可以支持任何在规则之前或之后公开运行代码的钩子的测试框架。从 RuleTester静态属性,分配正在运行的测试框架支持的以下任何一项。

¥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;

选项

¥Options

RuleTester 构造函数选项

¥RuleTester constructor options

import type { FlatConfig } from '@typescript-eslint/utils/ts-eslint';

import type { DependencyConstraint } from './DependencyConstraint';

/**
* Additional configuration options specific to @typescript-eslint/rule-tester.
*/
export interface RuleTesterConfig extends FlatConfig.Config {
/**
* The default filenames to use for type-aware tests.
* @default { ts: 'file.ts', tsx: 'react.tsx' }
*/
readonly defaultFilenames?: Readonly<{
ts: string;
tsx: string;
}>;
/**
* Constraints that must pass in the current environment for any tests to run.
*/
readonly dependencyConstraints?: DependencyConstraint;
}

有效的测试用例选项

¥Valid test case options

import type {
Linter,
Parser,
ParserOptions,
SharedConfigurationSettings,
} from '@typescript-eslint/utils/ts-eslint';

import type { DependencyConstraint } from './DependencyConstraint';

export interface TestLanguageOptions {
/**
* Environments for the test case.
*/
readonly env?: Readonly<Linter.EnvironmentConfig>;
/**
* The additional global variables.
*/
readonly globals?: Readonly<Linter.GlobalsConfig>;
/**
* The absolute path for the parser.
*/
readonly parser?: Readonly<Parser.LooseParserModule>;
/**
* Options for the parser.
*/
readonly parserOptions?: Readonly<ParserOptions>;
}

export interface ValidTestCase<Options extends readonly unknown[]> {
/**
* Function to execute after testing the case regardless of its result.
*/
readonly after?: () => void;
/**
* Function to execute before testing the case.
*/
readonly before?: () => void;
/**
* Code for the test case.
*/
readonly code: string;
/**
* Constraints that must pass in the current environment for the test to run
*/
readonly dependencyConstraints?: DependencyConstraint;
/**
* The fake filename for the test case. Useful for rules that make assertion about filenames.
*/
readonly filename?: string;
/**
* Language options for the test case.
*/
readonly languageOptions?: TestLanguageOptions;
/**
* Name for the test case.
*/
readonly name?: string;
/**
* Run this case exclusively for debugging in supported test frameworks.
*/
readonly only?: boolean;
/**
* Options for the test case.
*/
readonly options?: Readonly<Options>;
/**
* Settings for the test case.
*/
readonly settings?: Readonly<SharedConfigurationSettings>;
/**
* Skip this case in supported test frameworks.
*/
readonly skip?: boolean;
}

无效的测试用例选项

¥Invalid test case options

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> {
/**
* The data used to fill the message template.
*/
readonly data?: ReportDescriptorMessageData;
/**
* Reported message ID.
*/
readonly messageId: MessageIds;
/**
* 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> {
/**
* Constraints that must pass in the current environment for the test to run
*/
readonly dependencyConstraints?: DependencyConstraint;
/**
* 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 | string[] | null;
}

静态属性

¥Static Properties

以下每个属性都可以分配为 RuleTester 类的静态成员。

¥Each of the following properties may be assigned to as static members of the RuleTester class.

例如,要分配 afterAll

¥For example, to assign afterAll:

import { RuleTester } from '@typescript-eslint/rule-tester';

RuleTester.afterAll = () => {
// ...
};

afterAll

在此文件中的所有测试完成后运行。

¥Runs after all the tests in this file have completed.

describe

创建测试分组。

¥Creates a test grouping.

describeSkip

跳过在此 describe 内运行测试。

¥Skips running the tests inside this describe.

it

创建测试闭包。

¥Creates a test closure.

itOnly

跳过当前文件中的所有其他测试。

¥Skips all other tests in the current file.

itSkip

跳过运行此测试。

¥Skips running this test.