メインコンテンツへスキップ

クエリについて

概要

クエリとは、Testing Libraryがページ上の要素を見つけるために提供するメソッドです。いくつかの種類のクエリ("get"、"find"、"query")があります。これらの違いは、要素が見つからない場合にエラーをスローするか、Promiseを返して再試行するかどうかです。選択するページコンテンツに応じて、適切なクエリが異なります。ページを最もアクセスしやすい方法でテストするために、セマンティッククエリを使用する方法に関する推奨事項については、優先順位ガイドを参照してください。

要素を選択した後、イベントAPIまたはuser-eventを使用してイベントを発生させ、ページとのユーザーインタラクションをシミュレートしたり、Jestとjest-domを使用して要素に関するアサーションを作成したりできます。

クエリで動作するTesting Libraryヘルパーメソッドがあります。アクションに応じて要素が出現したり消失したりする場合、非同期APIwaitForfindByクエリなど)を使用して、DOMの変更を待つことができます。 特定の要素の子である要素のみを見つけるには、withinを使用できます。 必要に応じて、再試行のタイムアウトやデフォルトのtestID属性など、設定できるオプションもいくつかあります。

import {render, screen} from '@testing-library/react' // (or /dom, /vue, ...)

test('should show login form', () => {
render(<Login />)
const input = screen.getByLabelText('Username')
// Events and assertions...
})

クエリのタイプ

  • 単一要素
    • getBy...:クエリに一致するノードを返し、一致する要素がない場合*または*複数の一致が見つかった場合に、説明的なエラーをスローします(複数の要素が予想される場合は、代わりにgetAllByを使用してください)。
    • queryBy...:クエリに一致するノードを返し、一致する要素がない場合はnullを返します。これは、存在しない要素をアサートする場合に便利です。複数の一致が見つかった場合はエラーをスローします(これが問題ない場合は、代わりにqueryAllByを使用してください)。
    • findBy...:指定されたクエリに一致する要素が見つかったときに解決されるPromiseを返します。デフォルトのタイムアウト1000ミリ秒後に要素が見つからない場合、または複数の要素が見つかった場合、Promiseは拒否されます。複数の要素を見つける必要がある場合は、findAllByを使用してください。
  • 複数要素
    • getAllBy...:クエリに一致するすべてのノードの配列を返し、一致する要素がない場合はエラーをスローします。
    • queryAllBy...:クエリに一致するすべてのノードの配列を返し、一致する要素がない場合は空の配列([])を返します。
    • findAllBy...:指定されたクエリに一致する要素が見つかったときに、要素の配列に解決されるPromiseを返します。 デフォルトのタイムアウト `1000` ミリ秒後に要素が見つからない場合、Promiseは拒否されます.
      • findByメソッドは、getBy*クエリとwaitForの組み合わせです。 最後の引数としてwaitForオプションを受け入れます(例:await screen.findByText('text', queryOptions, waitForOptions))。
概要表

クエリのタイプ一致数 0一致数 1一致数 >1再試行 (非同期/Await)
単一要素
getBy...エラーをスロー要素を返すエラーをスローいいえ
queryBy...nullを返す要素を返すエラーをスローいいえ
findBy...エラーをスロー要素を返すエラーをスローはい
複数要素
Multiple Elementsエラーをスロー配列を返す配列を返すいいえ
queryAllBy...[]を返す配列を返す配列を返すいいえ
findAllBy...エラーをスロー配列を返す配列を返すはい

優先順位

ガイディングプリンシプルに基づき、テストは可能な限りユーザーがコード(コンポーネント、ページなど)と対話する方法に似ている必要があります。これを念頭に置いて、次の優先順位をお勧めします。

  1. **すべての人がアクセスできるクエリ** 視覚/マウスユーザーと支援技術を使用するユーザーの両方のエクスペリエンスを反映したクエリ。
    1. getByRole:これは、アクセシビリティツリーに公開されているすべての要素をクエリするために使用できます。 nameオプションを使用すると、返される要素をアクセシブル名でフィルタリングできます。 これは、ほとんどすべてのものにとって最優先事項であるべきです。 これでは取得できないものはあまりありません(取得できない場合、UIがアクセスできない可能性があります)。 ほとんどの場合、これはnameオプションと共に次のように使用されます:getByRole('button', {name: /submit/i})ロールのリストを確認してください。
    2. getByLabelText:このメソッドは、フォームフィールドに非常に適しています。ウェブサイトのフォームをナビゲートするとき、ユーザーはラベルテキストを使用して要素を見つけます。このメソッドはその動作をエミュレートするため、最優先事項であるべきです。
    3. getByPlaceholderTextプレースホルダーはラベルの代わりにはなりません。しかし、それしかない場合は、代替手段よりも優れています。
    4. getByText:フォーム以外では、テキストコンテンツはユーザーが要素を見つける主な方法です。このメソッドは、非インタラクティブな要素(div、span、段落など)を見つけるために使用できます。
    5. getByDisplayValue: フォーム要素の現在の値は、値が入力されたページをナビゲートする場合に役立ちます。
  2. **セマンティッククエリ** HTML5およびARIA準拠のセレクター。これらの属性と対話するユーザーエクスペリエンスは、ブラウザや支援技術によって大きく異なることに注意してください。
    1. getByAltText:要素がaltテキスト(imgareainput、およびカスタム要素)をサポートしている場合は、これを使用してその要素を見つけることができます。
    2. getByTitle:title属性は、スクリーンリーダーによって一貫して読み取られるとは限らず、視覚障碍のあるユーザーにはデフォルトでは表示されません。
  3. テストID
    1. getByTestId:ユーザーはこれらを見たり聞いたりすることができないため、ロールまたはテキストで一致できない場合、または意味がない場合(例:テキストが動的である場合)にのみ推奨されます。

クエリの使用

DOM Testing Libraryの基本クエリでは、最初の引数として`container`を渡す必要があります。Testing Libraryのほとんどのフレームワーク実装では、コンポーネントをレンダリングするときに、これらのクエリの事前バインドバージョンが提供されます。つまり、*コンテナを提供する必要はありません*。さらに、`document.body`をクエリする場合、以下に示すように`screen`エクスポートを使用できます(`screen`の使用をお勧めします)。

クエリへの主な引数は、_文字列_、_正規表現_、または_関数_です。ノードテキストの解析方法を調整するためのオプションもあります。クエリに渡すことができるものについては、TextMatchのドキュメントを参照してください。

次のDOM要素が与えられた場合(React、Vue、Angular、またはプレーンHTMLコードでレンダリングできます)

<body>
<div id="app">
<label for="username-input">Username</label>
<input id="username-input" />
</div>
</body>

クエリを使用して要素を見つけることができます(この場合はbyLabelText)

import {screen, getByLabelText} from '@testing-library/dom'

// With screen:
const inputNode1 = screen.getByLabelText('Username')

// Without screen, you need to provide a container:
const container = document.querySelector('#app')
const inputNode2 = getByLabelText(container, 'Username')

queryOptions

クエリタイプを含むqueryOptionsオブジェクトを渡すことができます。使用可能なオプションについては、各クエリタイプのドキュメントを参照してください(例:byRole API)。

screen

DOM Testing Libraryによってエクスポートされるすべてのクエリは、最初の引数として`container`を受け入れます。 `document.body`全体をクエリすることは非常に一般的であるため、DOM Testing Libraryは、`within`機能を使用して`document.body`に事前バインドされているすべてのクエリを持つ`screen`オブジェクトもエクスポートします。React Testing Libraryなどのラッパーは`screen`を再エクスポートするため、同じ方法で使用できます。

使用方法を次に示します

import {screen} from '@testing-library/dom'

document.body.innerHTML = `
<label for="example">Example</label>
<input id="example" />
`

const exampleInput = screen.getByLabelText('Example')

注意

`screen`を使用するには、グローバルDOM環境が必要です。jestを使用していて、testEnvironmentが`jsdom`に設定されている場合、グローバルDOM環境が使用可能になります。

テストを`script`タグでロードする場合は、`body`の後に配置してください。例はこちらにあります.

TextMatch

ほとんどのクエリAPIは、引数として`TextMatch`を受け取ります。つまり、引数は_文字列_、_正規表現_、またはシグネチャ`(content?:string、element?:Element | null)=> boolean`の_関数_で、一致の場合は`true`を返し、不一致の場合は`false`を返します。

TextMatchの例

次のHTMLが与えられた場合

<div>Hello World</div>

divを*見つけます*

// Matching a string:
screen.getByText('Hello World') // full string match
screen.getByText('llo Worl', {exact: false}) // substring match
screen.getByText('hello world', {exact: false}) // ignore case

// Matching a regex:
screen.getByText(/World/) // substring match
screen.getByText(/world/i) // substring match, ignore case
screen.getByText(/^hello world$/i) // full string match, ignore case
screen.getByText(/Hello W?oRlD/i) // substring match, ignore case, searches for "hello world" or "hello orld"

// Matching with a custom function:
screen.getByText((content, element) => content.startsWith('Hello'))

divを*見つけません*

// full string does not match
screen.getByText('Goodbye World')

// case-sensitive regex with different case
screen.getByText(/hello world/)

// function looking for a span when it's actually a div:
screen.getByText((content, element) => {
return element.tagName.toLowerCase() === 'span' && content.startsWith('Hello')
})

精度

`TextMatch`を受け取るクエリは、文字列マッチングの精度に影響を与えるオプションを含むことができるオブジェクトを最後の引数として受け入れます。

  • exact:デフォルトはtrueです。完全な文字列と大文字小文字を区別して一致させます。 falseの場合、部分文字列と一致し、大文字小文字は区別されません。
    • `regex`または`function`引数と一緒に使用しても効果はありません。
    • ほとんどの場合、文字列の代わりに正規表現を使用し、`{exact:false}`と組み合わせると、あいまい一致をより詳細に制御できるため、優先する必要があります。
  • normalizer:正規化の動作をオーバーライドするオプションの関数。 `Normalization`を参照してください。

正規化

DOMのテキストに対して一致ロジックを実行する前に、`DOM Testing Library`は自動的にそのテキストを正規化します。デフォルトでは、正規化はテキストの先頭と末尾から空白をトリミングし、**文字列内の隣接する複数の空白文字を単一のスペースに折りたたむ**ことからなります。

正規化を防ぐ、または代替の正規化を提供する(例:Unicode制御文字を削除する)場合は、オプションオブジェクトに`normalizer`関数を指定できます。この関数は文字列が与えられ、その文字列の正規化されたバージョンを返すことが期待されます.

注意

normalizer に値を指定すると、組み込みの正規化が *置き換え* られますが、getDefaultNormalizer を呼び出すことで、組み込みの正規化を取得できます。取得した正規化を調整したり、独自の正規化から呼び出したりすることができます。

getDefaultNormalizer は、動作を選択できるオプションオブジェクトを受け取ります。

  • trim: デフォルトは true です。前後の空白を削除します。
  • collapseWhitespace: デフォルトは true です。内部の空白(改行、タブ、連続するスペース)を単一のスペースに縮めます。

正規化の例

トリミングせずにテキストに対して一致を実行するには

screen.getByText('text', {
normalizer: getDefaultNormalizer({trim: false}),
})

正規化をオーバーライドして、一部のUnicode文字を削除し、組み込みの正規化動作の一部(すべてではない)を維持するには

screen.getByText('text', {
normalizer: str =>
getDefaultNormalizer({trim: false})(str).replace(/[\u200E-\u200F]*/g, ''),
})

手動クエリ

テストライブラリが提供するクエリの他に、通常の querySelector DOM API を使用して要素をクエリできます。クラスやIDでクエリするためにこれを使用することは推奨されません。なぜなら、それらはユーザーには見えないからです。TestIdを使用する必要がある場合は、セマンティックではないクエリにフォールバックするという意図を明確にし、HTMLで安定したAPIコントラクトを確立してください。

// @testing-library/react
const {container} = render(<MyComponent />)
const foo = container.querySelector('[data-foo="bar"]')

ブラウザ拡張機能

Testing Libraryのクエリの使い方にまだ問題がありますか?

「Testing Playground」という非常に便利なChromeブラウザ拡張機能があります。Testing Playground は、要素を選択するための最適なクエリを見つけるのに役立ちます。ブラウザの開発者ツールで要素階層を検査し、優れたテストプラクティスを奨励しながら、要素を選択する方法に関する提案を提供します。

プレイグラウンド

これらのクエリについてより詳しく知りたい場合は、testing-playground.com で試すことができます。Testing Playgroundは、独自のHTMLに対してさまざまなクエリを実行し、上記のルールに一致する視覚的なフィードバックを得ることができるインタラクティブなサンドボックスです。