Pro Yearly is on sale from $80 to $50! »

React Hooksのテストを書いてみよう

Df1674aa067667b855e74447f6003c85?s=47 markey
August 08, 2019

React Hooksのテストを書いてみよう

React Hooksのテストを書いてみよう

Df1674aa067667b855e74447f6003c85?s=128

markey

August 08, 2019
Tweet

Transcript

  1. React Hooks のテストを書い てみよう Meguro.es #22 08/08

  2. 自己紹介 名前: markey, markey_koichan(Twitter) 職業: フロントエンドエン ジニア スキル: React/Vue/TypeScript

  3. テスト、書いてますか?

  4. React Hooks のテスト、書い てますか?

  5. React Hooks 誕生後の世界 UI コンポーネント→Function Component ビジネスロジック→Custom Hook

  6. こんないいことが UI とロジックが分離され、テスタブルなコードに ビジネスロジックをCustom Hook に集約する(API からのデータ取 得・加工、状態、ハンドラー) UI コンポーネントの開発は受け取るProps

    の定義と、それらを元に した表示のみ UI コンポーネント開発者はロジック部分やAPI の仕様などを一切知 る必要がない 複数の開発者が同一ページのUI とロジックを並行開発することが可 能
  7. 今こそ、テストをやるチャン ス

  8. それぞれのテスト UI コンポーネント→Storybook + reg-suit ビジネスロジック→@testing-library/react-hooks

  9. 今日話すのはここ UI コンポーネント→Storybook + reg-suit ビジネスロジック→@testing-library/react-hooks ビジネスロジック→@testing-library/react-hooks

  10. @testing-library/react-hooks 公式ドキュメント React Hooks のテストユーティリティライブラリ hook をシンプルな関数のように実行でき( ラッパーコンポーネント は不要) 、

    hook の更新も容易 メインのファイルはわずか93 行
  11. パッケージの追加 yarn add --dev @testing-library/react-hooks yarn add --dev @testing-library/react-hooks #

    もしくは # もしくは npm install --save-dev @testing-library/react-hooks npm install --save-dev @testing-library/react-hooks
  12. @testing-library/react-hooks を使ったシンプルなテスト import import { { useState useState, , useCallback

    useCallback } } from from 'React' 'React' export export default default function function useCounter useCounter( () ) { { const const [ [count count, , setCount setCount] ] = = useState useState( (0 0) ) const const increment increment = = useCallback useCallback( (( () ) => => setCount setCount( (( (x x) ) => => x x return return { { count count, , increment increment } } } } import import { { renderHook renderHook } } from from '@testing-library/react-hook '@testing-library/react-hook import import useCounter useCounter from from '.' '.' test test( ('should use counter' 'should use counter', , ( () ) => => { {
  13. ブラウザ上でのhook の動作をシミュレートして、 値を更新するには? test test( ('should increment counter' 'should increment

    counter', , ( () ) => => { { const const { { result result } } = = renderHook renderHook( (( () ) => => useCounter useCounter( () )) ) expect expect( (result result. .current current. .count count) ). .toBe toBe( (0 0) ) // act でラップする // act でラップする act act( (( () ) => => { { result result. .current current. .increment increment( () ) } }) ) expect expect( (result result. .current current. .count count) ). .toBe toBe( (1 1) ) } }) )
  14. Context から値を取得するときはどうする? (Redux, Apollo Client, ...) const const CounterStepContext CounterStepContext

    = = React React. .createContext createContext( (1 1) ) export export const const CounterStepProvider CounterStepProvider = = ( ({ { step step, , children children } }) ) < <CounterStepContext CounterStepContext. .Provider value Provider value= ={ {step step} }> >{ {children children} }< < ) ) export export function function useCounter useCounter( () ) { { const const [ [count count, , setCount setCount] ] = = useState useState( (0 0) ) // increment する値はContext から取得 // increment する値はContext から取得 const const step step = = useContext useContext( (CounterStepContext CounterStepContext) ) const const increment increment = = useCallback useCallback( (( () ) => => setCount setCount( (( (x x) ) => => x x
  15. import import { { useCounter useCounter, , CounterStepProvider CounterStepProvider }

    } from from '.' '.' test test( ('should use custom step when incrementing' 'should use custom step when incrementing', , ( () ) => => const const wrapper wrapper = = ( ({ { children children } }) ) => => ( ( < <CounterStepProvider step CounterStepProvider step= ={ {2 2} }> >{ {children children} }< </ /CounterSt CounterSt ) ) // renderHook の第2 引数にwrapper オプションを指定 // renderHook の第2 引数にwrapper オプションを指定 const const { { result result } } = = renderHook renderHook( (( () ) => => useCounter useCounter( () ), , { { w w expect expect( (result result. .current current. .count count) ). .toBe toBe( (0 0) ) act act( (( () ) => => { { result result. .current current. .increment increment( () ) } }) )
  16. 非同期な関数の実行はどうする? export export default default function function useCounter useCounter( ()

    ) { { const const [ [count count, , setCount setCount] ] = = useState useState( (0 0) ) const const increment increment = = useCallback useCallback( (( () ) => => setCount setCount( (( (x x) ) => => x x // 0.1 秒後に非同期にincrement する // 0.1 秒後に非同期にincrement する const const incrementAsync incrementAsync = = useCallback useCallback( (( () ) => => setTimeout setTimeout( (i i return return { { count count, , increment increment, , incrementAsync incrementAsync } } } }
  17. test test( ('should increment counter after delay' 'should increment counter

    after delay', , async async ( () ) = = const const { { result result, , waitForNextUpdate waitForNextUpdate } } = = renderHook renderHook( (( () ) = = result result. .current current. .incrementAsync incrementAsync( () ) // 実行直後はまだincrement されていない // 実行直後はまだincrement されていない expect expect( (result result. .current current. .count count) ). .toBe toBe( (0 0) ) // Promise がresolve されるまで待つ // Promise がresolve されるまで待つ await await waitForNextUpdate waitForNextUpdate( () ) expect expect( (result result. .current current. .count count) ). .toBe toBe( (1 1) ) } }) )
  18. @testing-library/react-hooks で 快適なテストライフを

  19. ご清聴ありがとうございまし た! ( 副業募集中...React とTypeScript チョットカケマス...)