React Testing Libraryで基本的なReactコンポーネントをテストする使い方を説明します。
React Testing Libraryとは
(確認したバージョン 15.0.2 です)
Reactコンポーネントをレンダーして要素をテストする方法
与えられたpropsに対してコンポーネントが正しくレンダリングされるかをテストしたいことはよくあります。
render
でコンポーネントをレンダリング、screen
のクエリ系のメソッドで要素を取得し、expect
でアサーションをします。
また、@testing-library/jest-domを使うことで、DOM要素に対する便利なマッチャーを使えます。よく使うのは、次のようなメソッドです。
メソッド |
説明 |
toBeInTheDocument() |
要素が存在するか |
toHaveTextContent(...) |
要素のテキストが一致するか |
toHaveAttribute(...) |
要素の属性が一致するか |
import React from 'react'
import '@testing-library/jest-dom';
import { render, screen } from "@testing-library/react";
import Hello from "./hello";
describe('Hello', () => {
it('renders "Hey, stranger"', () => {
render(Hello );
expect(screen.getByText('Hey, stranger')).toBeInTheDocument();
});
it('renders "Hello, John" with the name props', () => {
render(Hello name="John" );
expect(screen.getByRole('heading', { level: 1 })).toHaveTextContent('Hello, John');
});
});
import React from "react";
export default function Hello(props) {
if (props.name) {
return h1Hello, {props.name}!h1;
} else {
return spanHey, strangerspan;
}
}
要素を取得する方法
React Testing Libraryで要素を取得するために、
getBy
(複数の要素の場合は getAllBy
)
findBy
(複数の要素の場合は findAllBy
)
queryBy
(複数の要素の場合は queryAllBy
)
また、要素の取得方法も、次のように様々な方法がサポートされています。
Role
:role属性で要素を取得
LabelText
: ラベルのテキストで要素を取得
PlaceholderText
: プレースホルダーのテキストで要素を取得
Text
: テキストで要素を取得
DisplayValue
:表示されている値で要素を取得
AltText
:alt属性で要素を取得
Title
:title属性で要素を取得。SVGなどで利用
TestId
:data-testid属性で要素を取得:
これらを組み合わせて、getByRole
やqueryAllByText
など目的に合わせて使い分けることができます。
getBy
/ findBy
/ queryBy
の使い分けとしては、要素がある場合は、getBy
、要素を待つ場合はfindBy
、要素がない場合はqueryBy
を使うと良いでしょう。
またgetAllBy
/ findAllBy
/ queryAllBy
は、複数の要素を取得する場合に使います。こちらも同様に、要素がある場合は、getAllBy
、要素を待つ場合はfindAllBy
、要素がない場合はqueryAllBy
を使うと良いでしょう。
各メソッドの早見表は次のとおりです。
メソッド |
要素が0件 |
要素が1件 |
要素が複数 |
Waitするか? |
getBy |
エラー |
1要素 |
エラー |
No |
findBy |
エラー |
1要素 |
エラー |
Yes |
queryBy |
null |
1要素 |
エラー |
No |
getAllBy |
エラー |
配列 |
配列 |
No |
findAllBy |
エラー |
配列 |
配列 |
Yes |
queryAllBy |
[] |
配列 |
配列 |
No |
参考: Testing LibraryのQueryについて
Reactコンポーネントのインタラクションテストをする方法
コンポーネントに対して、ユーザー操作をテストしたいこともよくあります。@testing-library/user-eventを使うことでユーザー操作をシミュレートすることができます。(他の方法もあります)
import React from 'react';
import '@testing-library/jest-dom';
import userEvent from '@testing-library/user-event'
import { render, screen } from "@testing-library/react";
import Toggle from "./toggle";
describe('Toggle', () => {
it('renders "Turn on" button', () => {
const onChange = jest.fn();
render(Toggle onChange={onChange} );
expect(screen.getByRole('button')).toHaveTextContent('Turn on');
expect(onChange).not.toHaveBeenCalled();
});
it('renders "Turn off" button after click and called onChange', async () => {
const onChange = jest.fn();
const user = userEvent.setup();
render(Toggle onChange={onChange} );
const button = screen.getByRole('button')
await user.click(button)
expect(button).toHaveTextContent('Turn off');
expect(onChange).toHaveBeenCalledWith(true);
});
});
import React, { useState } from "react";
export default function Toggle({ onChange }) {
const [state, setState] = useState(false);
return (
button
onClick={() => {
setState(previousState => !previousState);
onChange(!state);
}}
{state === true ? "Turn off" : "Turn on"}
button
);
}
APIからデータを非同期で取得して、その内容を表示するコンポーネントはよくあります。このようなコンポーネントをテストする場合、jest.mock
を使ってAPIのレスポンスをモック化し、waitFor
を使って非同期処理が終わるまで待つことができます。
import React from 'react'
import '@testing-library/jest-dom';
import { render, screen, waitFor } from "@testing-library/react";
import axios from 'axios';
import Posts from './posts';
jest.mock('axios');
describe('Posts', () => {
it('renders the posts', async () => {
const fakePosts = [
{ id: 1, title: 'First post' },
{ id: 2, title: 'Second post' },
];
axios.get.mockImplementation(() =>
Promise.resolve({
json: () => Promise.resolve(fakePosts)
})
);
render(Posts );
await waitFor(() => {
expect(screen.queryByText(/loading.../)).not.toBeInTheDocument();
});
expect(screen.getByText('First post')).toBeInTheDocument();
expect(screen.getByText('Second post')).toBeInTheDocument();
});
});
import React, { useState, useEffect } from "react";
import axios from "axios";
export default function Posts() {
const [posts, setPosts] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const fetchPosts = async () => {
const response = await axios.get('/posts').then((res) => res.json());
console.log(response);
setPosts(response);
setIsLoading(false);
};
useEffect(() => {
fetchPosts();
}, []);
if (isLoading) {
return "loading...";
}
return (
ul
{posts.map((post) => (
li key={post.id}{post.title}li
))}
ul
);
}
React Hooksのテスト方法
React Hooksをテストしたい場合は、renderHook
を使います。renderHook
はHooksをテストするためのユーティリティです。
import { renderHook } from '@testing-library/react';
import { waitFor } from "@testing-library/react";
import axios from 'axios';
import useLoggedInUser from './use-logged-in-user';
jest.mock('axios');
describe('useLoggedInUser', () => {
it('returns logged in user when logged in', async () => {
axios.get.mockResolvedValue({ name: 'Alice' });
const { result } = renderHook(() => useLoggedInUser());
await waitFor(() => {
expect(result.current.isLoading).toEqual(false)
});
expect(result.current.user).toEqual({ name: 'Alice' });
});
it('returns null when not logged in', async () => {
axios.get.mockResolvedValue(null);
const { result } = renderHook(() => useLoggedInUser());
await waitFor(() => {
expect(result.current.isLoading).toEqual(false)
});
expect(result.current.user).toEqual(null);
});
});
import { useState, useEffect } from 'react';
import axios from 'axios';
export default function useLoggedInUser() {
const [user, setUser] = useState(null);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
axios.get('/api/user')
.then((data) => {
setUser(data)
setIsLoading(false)
})
.catch(() => {
setIsLoading(false)
});
}, []);
return { isLoading, user };
};
参考: renderHookのAPI仕様
スナップショットテスト
JestのtoMatchSnapshot
を使うことで、コンポーネントのレンダリング結果をスナップショットとして保存し、次回のテスト時にスナップショットと比較することができます。
import React from 'react'
import '@testing-library/jest-dom';
import { render, screen } from "@testing-library/react";
import Hello from "./hello";
describe('Hello', () => {
it('renders Hello component', () => {
const { asFragment } = render(Hello );
expect(asFragment()).toMatchSnapshot();
})
});
作成されたスナップショットは次のようになります。今後Hello
コンポーネントを修正して、スナップショットと差分が出てくる場合は、スナップショットテストが失敗します。スナップショットの更新はjest --updateSnapshot
で行うことができます。
exports[`Hello renders Hello component 1`] = `
<DocumentFragment>
<span>
Hey, stranger
</span>
</DocumentFragment>
`;
より高度なことをしたい場合