react-testing-libraryで「要素が存在しないこと」をテストする方法

Reactのテストライブラリでreact-testing-libraryを使っているところは多いと思う。そこで、「ある要素が存在すること」をテストしたいことも多いだろう。そんな時は以下のように書けばいい。

it('画面に initial が表示されること', () => {
  const { getByText } = render(<App />);
  expect(getByText('initial')).toBeTruthy()
});

ただ、「ある要素が存在しないこと」をテストするにはどうすればいいのだろうか。例えばこんな風に書いてみる。

it('画面に initial が表示されること', () => {
  const { getByText } = render(<App />);
  expect(getByText('initial')).toBeTruthy();
  expect(getByText('hoge')).toBeFalsy();
});

これを実行してみると失敗する。エラーにはこんなことが書いてある。

    TestingLibraryElementError: Unable to find an element with the text: hoge. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.

なるほど。getByTextはテキストが存在しなかった場合にエラーを返してしまうらしい。つまり、getByTextにtoBeFalsyを繋げるような書き方ではテキストが存在しないことは確認できない。ドキュメントを見ても、getBy系メソッドは要素が見つからなかった場合にエラーを返すと書いてある。getByTestIdに変えても同じ挙動になる。ではどうすればいいのか?

queryByを使う

queryByはgetByと違い、要素が見つからなくてもエラーを返さずにnullを返すようになっている。

it('画面に initial が表示されること', () => {
  const { getByText } = render(<App />);
  expect(getByText('initial')).toBeTruthy();
  expect(queryByText('hoge')).toBeNull();
});

こんな感じでqueryBy~toBeNull にすれば、要素が存在しないことをテストできる。

getBy, queryBy, findByの違い

react-testing-libraryのクエリメソッドにはgetBy, queryBy, findByの3種類のタイプがある。ご丁寧なことに ドキュメント を見るとその違いがちゃんと書いてある。

ざっくりまとめると.

  • getBy: 要素を探してくるよ。見つかったらその要素を返すよ、見つからなかったらエラーになるよ。
  • queryBy: 要素を探してくるよ。見つかったらその要素を返すよ、見つからなかったらnullを返すよ。
  • findBy: 要素を探してくるよ。見つかったらその要素をresolveするPromiseを返すよ。見つからなかったらデフォルトタイムアウト1000msまでリトライするけど、それでもダメならrejectするよ。

findByでリトライができるのには驚いたが、実際には非同期を待つにはwaitForを使うことが多いよなあという印象。私の現場だけかもしれないが。