Skip to content

feat: add no-test-id-queries rule #1006

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
May 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,7 @@ module.exports = [
| [no-node-access](docs/rules/no-node-access.md) | Disallow direct Node access | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-svelte][] ![badge-vue][] | | |
| [no-promise-in-fire-event](docs/rules/no-promise-in-fire-event.md) | Disallow the use of promises passed to a `fireEvent` method | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-svelte][] ![badge-vue][] | | |
| [no-render-in-lifecycle](docs/rules/no-render-in-lifecycle.md) | Disallow the use of `render` in testing frameworks setup functions | ![badge-angular][] ![badge-marko][] ![badge-react][] ![badge-svelte][] ![badge-vue][] | | |
| [no-test-id-queries](docs/rules/no-test-id-queries.md) | Ensure no `data-testid` queries are used | | | |
| [no-unnecessary-act](docs/rules/no-unnecessary-act.md) | Disallow wrapping Testing Library utils or empty callbacks in `act` | ![badge-marko][] ![badge-react][] | | |
| [no-wait-for-multiple-assertions](docs/rules/no-wait-for-multiple-assertions.md) | Disallow the use of multiple `expect` calls inside `waitFor` | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-svelte][] ![badge-vue][] | | |
| [no-wait-for-side-effects](docs/rules/no-wait-for-side-effects.md) | Disallow the use of side effects in `waitFor` | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-svelte][] ![badge-vue][] | | |
Expand Down
32 changes: 32 additions & 0 deletions docs/rules/no-test-id-queries.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Ensure no `data-testid` queries are used (`testing-library/no-test-id-queries`)

<!-- end auto-generated rule header -->

## Rule Details

This rule aims to reduce the usage of `*ByTestId` queries in your tests.

When using `*ByTestId` queries, you are coupling your tests to the implementation details of your components, and not to how they behave and being used.

Prefer using queries that are more related to the user experience, like `getByRole`, `getByLabelText`, etc.

Example of **incorrect** code for this rule:

```js
const button = queryByTestId('my-button');
const input = screen.queryByTestId('my-input');
```

Examples of **correct** code for this rule:

```js
const button = screen.getByRole('button');
const input = screen.getByRole('textbox');
```

## Further Reading

- [about `getByTestId`](https://testing-library.com/docs/queries/bytestid)
- [about `getByRole`](https://testing-library.com/docs/queries/byrole)
- [about `getByLabelText`](https://testing-library.com/docs/queries/bylabeltext)
- [Common mistakes with React Testing Library - Not querying by text](https://kentcdodds.com/blog/common-mistakes-with-react-testing-library#not-querying-by-text)
47 changes: 47 additions & 0 deletions lib/rules/no-test-id-queries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { TSESTree } from '@typescript-eslint/utils';

import { createTestingLibraryRule } from '../create-testing-library-rule';
import { ALL_QUERIES_VARIANTS } from '../utils';

export const RULE_NAME = 'no-test-id-queries';
export type MessageIds = 'noTestIdQueries';
type Options = [];

const QUERIES_REGEX = `/^(${ALL_QUERIES_VARIANTS.join('|')})TestId$/`;

export default createTestingLibraryRule<Options, MessageIds>({
name: RULE_NAME,
meta: {
type: 'problem',
docs: {
description: 'Ensure no `data-testid` queries are used',
recommendedConfig: {
dom: false,
angular: false,
react: false,
vue: false,
svelte: false,
marko: false,
},
},
messages: {
noTestIdQueries:
'Using `data-testid` queries is not recommended. Use a more descriptive query instead.',
},
schema: [],
},
defaultOptions: [],

create(context) {
return {
[`CallExpression[callee.property.name=${QUERIES_REGEX}], CallExpression[callee.name=${QUERIES_REGEX}]`](
node: TSESTree.CallExpression
) {
context.report({
node,
messageId: 'noTestIdQueries',
});
},
};
},
});
2 changes: 1 addition & 1 deletion tests/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { resolve } from 'path';

import plugin from '../lib';

const numberOfRules = 27;
const numberOfRules = 28;
const ruleNames = Object.keys(plugin.rules);

// eslint-disable-next-line jest/expect-expect
Expand Down
86 changes: 86 additions & 0 deletions tests/lib/rules/no-test-id-queries.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import rule, { RULE_NAME } from '../../../lib/rules/no-test-id-queries';
import { createRuleTester } from '../test-utils';

const ruleTester = createRuleTester();

const SUPPORTED_TESTING_FRAMEWORKS = [
'@testing-library/dom',
'@testing-library/angular',
'@testing-library/react',
'@testing-library/vue',
'@marko/testing-library',
];

const QUERIES = [
'getByTestId',
'queryByTestId',
'getAllByTestId',
'queryAllByTestId',
'findByTestId',
'findAllByTestId',
];

ruleTester.run(RULE_NAME, rule, {
valid: [
`
import { render } from '@testing-library/react';

test('test', async () => {
const { getByRole } = render(<MyComponent />);

expect(getByRole('button')).toBeInTheDocument();
});
`,

`
import { render } from '@testing-library/react';

test('test', async () => {
render(<MyComponent />);

expect(getTestId('button')).toBeInTheDocument();
});
`,
],

invalid: SUPPORTED_TESTING_FRAMEWORKS.flatMap((framework) =>
QUERIES.flatMap((query) => [
{
code: `
import { render } from '${framework}';

test('test', async () => {
const { ${query} } = render(<MyComponent />);

expect(${query}('my-test-id')).toBeInTheDocument();
});
`,
errors: [
{
messageId: 'noTestIdQueries',
line: 7,
column: 14,
},
],
},
{
code: `
import { render, screen } from '${framework}';

test('test', async () => {
render(<MyComponent />);

expect(screen.${query}('my-test-id')).toBeInTheDocument();
});
`,
errors: [
{
messageId: 'noTestIdQueries',
line: 7,
column: 14,
},
],
},
])
),
});