高次コンポーネントを使用した React Native Jest テストのセットアップ


あなたの React Native アプリが私たちのアプリのようなものなら、画面と画面コンポーネントをラップする、ラッパーの上にラッパーがあります.いくつかの例は次のとおりです.

  • SafeAreaProvider - デバイスの安全な領域にのみアクセスしていることを確認するため

  • ThemeProvider - Styled Components のようなものを使用して、アプリ全体にテーマ コンテキストを提供していたとします

  • Redux - アプリ全体の状態を管理するため

  • これにより、単体テストと統合テストを作成する際に注意が必要になります.コンポーネントが、1 つ以上の高次コンポーネント (HoC) によって提供されるものに誤って依存する可能性があるためです.

    Jest テストのセットアップを簡素化するために、必要な HoC をテストごとに簡単に利用できるようにするヘルパー関数をいくつか作成しました.物事を単純化するということは、開発時間を短縮しながら、より多くのテストを作成する障壁を下げることを意味するため、これは大きなメリットです. 🎉

    Typescript でそれを行う方法の例を次に示します.私たちが使用する外部パッケージは、Redux Toolkit、Styled Components、および React Native Safe Area Context です.

    // testHelpers.tsx
    
    import * as React from 'react'
    import { getDefaultMiddleware } from '@reduxjs/toolkit'
    import lodash from 'lodash'
    import { SafeAreaProvider } from 'react-native-safe-area-context'
    import { Provider as ReduxProvider } from 'react-redux'
    import renderer, { ReactTestInstance } from 'react-test-renderer'
    import createMockStore from 'redux-mock-store'
    import { ThemeProvider } from 'styled-components/native'
    
    import { TRootState } from '@app/core/state/root'
    import { initialState } from '@app/core/state/mockedInitialState'
    import { theme } from '@app/themes'
    
    type DeepPartial<T> = {
      [P in keyof T]?: DeepPartial<T[P]>
    }
    
    type TConfig = {
      mockRedux?: boolean
      mockSafeAreaProvider?: boolean
      mockTheme?: boolean
      state?: DeepPartial<TRootState>
    }
    
    const initialMetrics = {
      frame: { height: 0, width: 0, x: 0, y: 0 },
      insets: { bottom: 0, left: 0, right: 0, top: 0 },
    }
    
    export function createMockedElement(element: React.ReactElement, config?: TConfig) {
      let mockedElement = element
    
      if (config?.mockRedux !== false) {
        const middlewares = getDefaultMiddleware()
        const mockStore = createMockStore(middlewares)
        const state = lodash.merge(initialState, config?.state)
        const store = mockStore(state)
        mockedElement = <ReduxProvider store={store}>{mockedElement}</ReduxProvider>
      }
    
      if (config?.mockTheme !== false) {
        mockedElement = <ThemeProvider theme={theme}>{mockedElement}</ThemeProvider>
      }
    
      if (config?.mockSafeAreaProvider !== false) {
        mockedElement = <SafeAreaProvider initialMetrics={initialMetrics}>{mockedElement}</SafeAreaProvider>
      }
    
      return mockedElement
    }
    
    export function createReactTestInstance(element: React.ReactElement, config?: TConfig): ReactTestInstance {
      return renderer.create(createMockedElement(element, config)).root
    }
    


    ここでは非常に多くのことが起こっているので、それを分解しましょう.しかし、最初に、次のことについて話し合う必要があります...

    ヘルパー関数の実際の使用方法



    これらのヘルパー メソッドを実際にどのように使用したいかを最初に理解する方が簡単だといつも思います.したがって、これらのヘルパーをテストに統合する方法の例を追加しました.これは React’s Test Renderer を使用することに注意してください.これは、たとえば、予想される要素の存在をチェックするのに役立ちます.

    import { createReactTestInstance } from './testHelpers'
    
    describe('MyComponent tests', () => {
      it('renders correct version for users who shown interest', () => {
        const instance = createReactTestInstance(<MyComponent />)
    
        expect(instance.findByProps({ testID: `interested-icon` })).toBeTruthy()
      })
    
      it('renders correct version for users who have not shown interest', () => {
        const instance = createReactTestInstance(<MyComponent />)
    
        expect(instance.findByProps({ testID: `not-interested-icon` })).toBeTruthy()
      })
    })
    


    特定のユーザー アクションが特定の期待をもたらすかどうかをテストしたい場合は、React Testing Library (React のテスト レンダラーの上にある) が最適です. createReactTestInstance ヘルパーを使用する代わりに、createMockedElement ヘルパーを利用することができます.例を次に示します.

    import { fireEvent, render } from '@testing-library/react-native'
    import { act } from 'react-test-renderer'
    
    import { createMockedElement } from './testHelpers'
    
    const navigateMock = jest
      .mock
      // your mock...
      ()
    
    describe('BackButton tests', () => {
      it('navigates to the right screen onPress', async () => {
        const mockedElement = createMockedElement(<BackButton previousScreen="PreviousScreenName" />)
    
        const renderAPI = await render(mockedElement)
    
        await act(async () => {
          const backButton = renderAPI.getByTestId('button-back-navigation')
          await fireEvent.press(backButton)
          expect(navigateMock).toHaveBeenCalledWith('PreviousScreenName')
        })
      })
    })
    


    ヘルパー関数が実際にどのように使用されるかを理解したので、ヘルパー ファイルの設定方法に戻りましょう.

    ヘルパーファイルの壊れ方



    このファイルの中心にあるのは createMockedElement 関数です.

    export function createMockedElement(element: React.ReactElement, config?: TConfig) {
      let mockedElement = element
    
      if (config?.mockRedux !== false) {
        const middlewares = getDefaultMiddleware()
        const mockStore = createMockStore(middlewares)
        const state = lodash.merge(initialState, config?.state)
        const store = mockStore(state)
        mockedElement = <ReduxProvider store={store}>{mockedElement}</ReduxProvider>
      }
    
      if (config?.mockTheme !== false) {
        mockedElement = <ThemeProvider theme={theme}>{mockedElement}</ThemeProvider>
      }
    
      if (config?.mockSafeAreaProvider !== false) {
        mockedElement = <SafeAreaProvider initialMetrics={initialMetrics}>{mockedElement}</SafeAreaProvider>
      }
    
      return mockedElement
    }
    


    この関数は、テストする要素/コンポーネントと、オプションの config オブジェクトの 2 つの引数を取ります.この構成オブジェクトを使用すると、テスト中にコンポーネントをレンダリングするときに含めるラッパーを指定できます (存在する場合).たとえば、Redux の状態をモックする必要がある場合は、次のようにテストをセットアップできます.

    it("doesn't open the modal when row is active", async () => {
      const mockedState = { show_modal: false }
      const config = { state: mockedState }
    
      const mockedElement = createMockedElement(<Row />, config)
    
      const renderAPI = await render(mockedElement)
    
      await act(async () => {
        // ... your test expectations
      })
    })
    

    ThemeProvider および/または SafeAreaProvider ラッパーを含める必要がある場合も、同様に同じことができます. TConfig で定義されているように、これら 2 つのオプションは boolean の入力を受け取ることに注意してください.

    Redux 状態の設定について深く掘り下げる



    Redux の状態をモックするときは、テスト用の Redux の状態がいくつかの初期値で設定されていることを確認する必要があります.これを行うために、さまざまな Redux Toolkit スライスからすべての初期状態を抽出し、それを 1 つのオブジェクトに結合し、それを lodash マージ関数に渡しました (モックされた状態と深くマージされるようにするため).

    // @app/core/state/mockedInitialState
    
    import { initialStateFeature1 } from '@covid/core/state/feature1.slice'
    import { initialStateFeature2 } from '@covid/core/state/feature2.slice'
    import { initialStateFeature3 } from '@covid/core/state/feature3.slice'
    
    export const initialState: TRootState = {
      feature1: initialStateFeature1,
      feature2: initialStateFeature2,
      feature3: initialStateFeature3,
    }
    


    以上です!これにより、React Native のテスト作業が少し楽になることを願っています. 😄 何か提案や改善点がありましたら、お知らせください.テスト ゲームの改善に常に力を入れています.私は https://bionicjulia.com にいます.