Skip to main content

rule-tester


@typescript-eslint/rule-tester

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

这是 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.

用法

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

英: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 />;',
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 文件夹,其中包含三个文件:

英:Type-aware rules can be tested in almost exactly the same way, except you need to create some files on disk. We require files on disk due to a limitation with TypeScript in that it requires physical files on disk to initialize the project. We suggest creating a fixture folder nearby that contains three files:

  1. file.ts - 这应该是一个空文件。
  2. react.tsx - 这应该是一个空文件。
  3. tsconfig.json - 这应该是用于测试的配置,例如:
    {
    "compilerOptions": {
    "strict": true
    },
    "include": ["file.ts", "react.tsx"]
    }
提醒

需要注意的是,file.tsreact.tsx 都必须是空文件! 规则测试器将自动使用测试中的字符串内容 - 空文件仅用于初始化。

然后,你可以通过提供类型感知配置来测试你的规则:

英:You can then test your rule by providing the type-aware config:

const ruleTester = new RuleTester({
parserOptions: {
tsconfigRootDir: './path/to/your/folder/fixture',
project: './tsconfig.json',
},
});

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

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

测试依赖约束

有时需要针对依赖的多个版本测试你的规则,以确保向后和向前兼容性。 向后兼容性测试会带来一些复杂性,因为某些测试可能与旧版本的依赖不兼容。 例如 - 如果你正在针对旧版本的 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!

import type { RangeOptions } from 'semver';

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 允许你在单个测试或构造函数级别应用依赖约束。

英: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.

具有特定框架

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 的静态属性。

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;

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;

选项

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<TOptions 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<TOptions>;
/**
* 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<TMessageIds extends string> {
/**
* Reported message ID.
*/
readonly messageId: TMessageIds;
/**
* 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<TMessageIds 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: TMessageIds;
/**
* Reported suggestions.
*/
readonly suggestions?: readonly SuggestionOutput<TMessageIds>[] | 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<
TMessageIds extends string,
TOptions extends Readonly<unknown[]>,
> extends ValidTestCase<TOptions> {
/**
* Expected errors.
*/
readonly errors: readonly TestCaseError<TMessageIds>[];
/**
* 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;
}