【Jest, enzyme】 useSelectorやuseDispatchを使ったFunctionComponentをうまくテストする
ReactのテストでJestやenzymeを使うことは多いと思うのですが、React v16.8以降でFunctionComponentでの書き方を使う機会が増えたにもかかわらず、useSelectorやuseDispatchあたりをclassでなくFunctionComponentで使ったテストケースがあまり見つからず困ったのでサンプルをいくつか書いてみます。
typescriptで書いてますが、使わない場合はよしなに読み替えていただければと思います。
The product code
かなり単純化したものになりますが、下記のようなコードに対してテストを書きたい場合です。
import React from 'react' import { useDispatch, useSelector } from 'react-redux' import * as actions from './actions' const UserList: React.FC = () => { const dispatch = useDispatch() const users = useSelector(state => state.reducer.users) const companies = useSelector(state => state.reducer.companies) const submit = (userId: number) => { dispatch(actions.postUser(userId)) } return ( <div> {users.map(user => <div> <p>{user.name}</p> <button onClick={() => submit(user.id)}>submit</button> </div> )} </div> ) }
Writing Test code with mocking
単純なのですが、いろいろハマるところがあったのでひとつずつ解いていきます。
※ やり方はここで書く以外にも何通りかあります
# UserList.spec.tsx import React from 'react' import { useSelector, useDispatch } from 'react-redux' import { shallow } from 'enzyme' import * as actions from './actions' jest.mock('react-redux') // -> ① jest.mock('./actions') // -> ②
まず、① react-redux
をimportして jest.mock
しておくのが最初のポイントです。② actionsもここでmockしておくと良いです。
const useSelector = useSelector as jest.mock // -> ③
useSelectorはここでmockしましょう。いよいよ具体的にテストを書いていきます。
describe('UserList', () => { afterEach(() => { jest.resetAllMocks() // -> ④ }) let wrapper beforeEach(() => { const users = [{ id: 1, name: 'userName' }] as User[] const companies = [{ id: 1, divisionName: 'R&D' }] useSelectorMock.mockReturnValueOnce(users) // -> ⑤ useSelectorMock.mockReturnValueOnce(companies) // -> ⑥ useDispatch.mockReturnValue(jest.fn()) // -> ⑦ wrapper = shallow(<UserList />) }) it('renders view', () => { expect(wrapper.text()).toEqual( expect.stringContaining('userName') ) }) })
これでテストには通ると思います。④でafterEachとしてmock化の解除を行います。beforeEachの中が需要で、⑤⑥でReturnValueOnceを設定することで、それぞれuseSelectorの返すべき値をusersとcompaniesに振り分けています。simulateなどで再レンダリングさせる際は、このペアをもう一度事前に登録しておくことを忘れないようにしてください。⑦はuseDispatchのmockです。
Additionally
ついでに、actionのテストも簡単に見てみましょう。
describe('click the button', () => { let spy beforeEach(() => { // -> actionsは最初にmock化してあります spy = jest.spyOn(actions, 'postUser') // -> 先程のbeforeEachが効いているscope内として、 // あと2つ再レンダリング用に書きます useSelectorMock.mockReturnValueOnce(users) useSelectorMock.mockReturnValueOnce(companies) }) it('calls api', () => { wrapper.find('button').simulate('click') expect(spy).toHaveBeenCalled() }) })
こんな感じです。 import * as actions
のmock化とかも地味に躓きましたが、上記のような感じで上手く行けると思います。他にも act
周りとかで細かいtipsはあるのですが、それはまたの機会に。enzymeのshallow, mountになれるのもjestテストを快適に書くポイントですよね。