🐡
React Testing Library の render で独自のオプションを渡す
今のプロジェクトで React Component のテストを Enzyme から React Testing Library に移行する、という苦行刺激的なチャレンジをしているので、気づいたことがあればその都度書き残していきたい。
- Enzyme は Component に実装に依存しているため、メンテが大変
- 一方、React Testing Library は Render 結果である DOM 構造をテストするので分かりやすい。実際のユーザー行動に近い
- しかし、異なるコンセプトのもとに書かれたコードを移行するので、「やりたいこと」に対するアプローチの違いをいちいち変換してあげる必要がある。
- コンポーネントのインスタンスメソッドを Mock しているようなテストだと対象コンポーネントの作り自体を変えないと無理っぽい
Custom Render で Wrapper Component にオプションを渡したい
移行の中で最初に困ったのがこれ。
React Testing Library では、公式ドキュメントに記載されているように、独自の render メソッドを実装して、対象のコンポーネントを Context Provider でラップできる。たとえば、以下のように Redux や React Router を設定できる。
import React from 'react'
import { render } from '@testing-library/react'
import { Router } from 'react-router-dom';
import { createMemoryHistory, MemoryHistory } from 'history';
import configureStore from 'redux-mock-store';
import { Provider } from 'react-redux';
const wrapper = ({ children }) => {
const mockStore = configureStore();
const store = mockStore({
session: {},
books: {},
});
const history = createMemoryHistory();
return (
<Provider store={store}>
<Router history={history}>
{children}
</Router>
</Provider>
);
};
const customRender = (ui, options) =>
render(ui, { wrapper, ...options })
// re-export everything
export * from '@testing-library/react'
// override render method
export { customRender as render }
このカスタム render を使う側では、特にコードを変えずに import を変えるだけでいい。
import { render } from '../test-utils';
...
render(<MyComponent {...props}>);
しかし、単純な用途ではこれでいいのだが、複雑な条件のテストでは wrapper
関数にオプションを渡して、Redux store の初期設定をしたくなる。これの実現方法は、以下のように少し手を加えればできる[1]。
const wrapper = ({ children }, options = {}) => {
const mockStore = configureStore();
const store = mockStore(
_.merge(
{
session: {},
books: {},
},
options.store
)
);
...
};
const customRender = (ui, options = {}) =>
render(ui, { wrapper: props => wrapper(props, options.wrapperOptions), ...options });
ここでは日和って lodash の merge() を使った。使う側ではこう。
render(<MyComponent {...props} />, {
wrapperOptions: {
store: {
books: { books: [book1, book2] }
}
}
});
Discussion