@testing-library/react-hookとJestでReact Hookをテストする
「React #2 Advent Calendar 2020」4日目の記事です。 @testing-library/react-hookを使うと、React Hookのユニットテストがかけます。 Install テスト対 […]
広告ここから
広告ここまで
目次
「React #2 Advent Calendar 2020」4日目の記事です。
@testing-library/react-hook
を使うと、React Hookのユニットテストがかけます。
Install
$ yarn add -D react-@testing-library/react-hooks react-test-renderer
テスト対象のフックを作ってみる
動作確認のために、適当なフックを作ってみましょう。
const useExampleModalHook = () => {
const [modal, modalOpen] = useState(false)
const open = useCallback(() => {
modalOpen(true)
}, [modalOpen])
const close = useCallback(() => {
modalOpen(false)
}, [modalOpen])
const toggle = useCallback(() => {
modalOpen(!modal)
}, [modal, modalOpen])
return {
modal,
open,
close,
toggle,
}
}
テストコードはこんな感じです。
import { renderHook, act } from '@testing-library/react-hooks';
describe('useExampleModalHook', () => {
let result = renderHook(() => useExampleModalHook()).result
beforeEach(() => {
result = renderHook(() => useExampleModalHook()).result
})
afterEach(() => {
mockDispatch.mockClear();
});
it('should modal is false by default', () => {
expect(result.current.modal).toEqual(false)
})
it('should modal is true when call open', () => {
act(() => {
result.current.open()
})
expect(result.current.modal).toEqual(true)
})
it('should modal is true when called toggle once', () => {
act(() => {
result.current.toggle()
})
expect(result.current.modal).toEqual(true)
})
it('should modal is true when called toggle once', () => {
act(() => {
result.current.toggle()
})
act(() => {
result.current.toggle()
})
expect(result.current.modal).toEqual(false)
})
})
renderHook
を使うことで、hook単体でテストコードが書けるようになります。
また act
を使って関数を実行したり、unmountなどもテストしていけます。
With Redux (useDispatch / useSelector)
ただしReduxが絡むとモックが必要になります。
export const exampleHookWithRedux = () => {
const dispatch = useDispatch();
const globalClose = useCallback(() => {
dispatch(closeUserModal('username'));
}, [dispatch]);
return {
dummy: useSelector((state: State) => state.modal),
globalClose,
}
}
以下のようにJestでモックしてやりましょう。
const mockDispatch = jest.fn();
const mockSelector = jest.fn().mockImplementation(() => ({
status: '',
result: '',
}));
jest.mock('react-redux', () => ({
useDispatch: () => mockDispatch,
useSelector: () => mockSelector(),
}));
describe('exampleHookWithRedux ', () => {
let result = renderHook(() => exampleHookWithRedux ()).result
beforeEach(() => {
result = renderHook(() => exampleHookWithRedux ()).result
})
it('should modal is false by default', () => {
expect(result.current.dummy).toEqual({
status: '',
result: '',
})
})
it('should call dispatch function at once', () => {
act(() => {
result.current.globalClose()
})
expect(mockDispatch).toHaveBeenCalledTimes(1)
})
it('should call valid action', () => {
act(() => {
result.current.globalClose()
})
expect(mockDispatch).toHaveBeenCalledWith({
type: 'CLOSE_USER_MODAL',
payload: {
username: 'username'
}
})
})
it('When selector return updated value, should get updated one', () => {
mockSelector.mockImplementationOnce(() => ({
status: 'loading',
result: ''
}))
result = renderHook(() => exampleHookWithRedux ()).result
expect(result.current.dummy).toEqual({
status: 'loading',
result: '',
})
})
})