material-uiのTextFieldをtesting-library/reactでテストしようとすると、HTMLElementを取り出すところで苦労したのでメモを残す。
TextFieldに付与したaria-labelはinputのラッパー要素に付く
次のTextFieldがどこかのコンポーネントにあるとする。
<TextField name="name" label="name" aria-label="name" variant="outlined" value="title" />
実際にDOMにレンダリングすると次の構造になる。ルートの要素にaria-label
がついていることがわかる。
<div aria-label="name" class="MuiFormControl-root MuiTextField-root" > <label class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-shrink MuiInputLabel-outlined MuiFormLabel-filled" data-shrink="true" > name </label> <div class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-formControl" > <input aria-invalid="false" class="MuiInputBase-input MuiOutlinedInput-input" name="name" type="text" value="title" /> <fieldset aria-hidden="true" class="PrivateNotchedOutline-root-1 MuiOutlinedInput-notchedOutline" > <legend class="PrivateNotchedOutline-legendLabelled-3 PrivateNotchedOutline-legendNotched-4" > <span> name </span> </legend> </fieldset> </div> </div>
testing-libraryのgetByLabelText
関数は、ラベル名でHTMLElementを取り出すので、input要素に直接アクセスできないことがわかる。解決策として2つの方法がある。
querySelector関数を使う
1つ目の方法はgetByLabelText
関数でルート要素を取り出したあと、querySelector
関数を使ってinput要素を探すというもの。
import { render, cleanup } from "@testing-library/react/pure"; import "@testing-library/jest-dom/extend-expect"; import { TextField } from "@material-ui/core"; describe("Material UIのTextFieldのテスト", () => { it("querySelectorを使ってvalueの値をチェックする", () => { const result = render( <TextField name="name" label="name" aria-label="name" variant="outlined" value="title" /> ); // input要素を取り出す。 const input = result.getByLabelText("name").querySelector("input"); expect(input?.value).toEqual("title"); }); });
querySelector("input")
の戻り値はHTMLInputElement | null
なので適宜nullチェックを加える必要があるが、値を検証するだけならinput?.value
とするだけで十分。
getByDisplayValue関数を使う
2つめの方法は、getByDisplayValue関数を使ってvalue値からinput要素を取り出す。
describe("Material UIのTextFieldのテスト", () => { it("getByDisplayValueを使ってvalueの値をチェックする", () => { const result = render( <div> <div>title</div> <TextField name="name" label="name" aria-label="name" variant="outlined" value="title" /> </div> ); const input = result.getByDisplayValue("title"); // elementが取り出せた時点で 'title' というvalueを持っていることがわかるので // このexpectは実際は不要 expect(input.getAttribute("value")).toEqual("title"); }); });
getByDisplayValue
関数はinput要素系のための関数なので便利なのだが、値を使って取り出すので同じ値のinput要素があったりするとエラーになってしまう。
おわりに
個人的にはラベルを指定した上でquerySelector
関数を使う方法が好きですが、しかしMaterial UIの内部構造を知った上で書く必要があるのでちょっとな〜という気はしますが仕方ない気もする。