JESTおよび反応試験ライブラリによる反応成分試験



Responseコンポーネントをテストすると、ユーザーがそれと対話するときにコンポーネントが機能すると確信します.私の最初の仕事のジュニア完全なスタック開発者として、私はそれが非常に私の現在のcodebaseを理解するのに役立つだけでなく、私は学習中に値を追加できるようになりました.
この記事は、私が研究している間に役に立つ情報と、遭遇したいくつかの課題に対する答えの要約です.私は、車輪を再発明することを望みません、しかし、彼らの経歴の同様のステージで他のものを助けるために.また、テストを書く際にいくつかの経験があると仮定します.

なぜJest and RTL (React Testing Library) ?
反応はOpenFeintをテストランナーとしてjestを推奨します(おそらく彼らはそれを維持するため)とRTLの選択のテストユーティリティとして.Jestテストは非常に高速です、それは設定するのは簡単です、そして、それは特定の機能を交換して、望ましい値を返すか、テスト主題がどのように機能を実行しているかについて確認することができるmock機能のような多くの強力な機能を持ちます.RTLは非常に簡単に設定するのは簡単です(非同期を含む)クエリを作成し、どのように構築されたため、それは良いテストを書くのを助けるでしょう.
Jest-Dom 必須ではありませんが、Jest Matcherを拡張しているので、テストを簡単に書くことができます.toBe() , toHaveBeenCalled() ) また、より明確なテストを書くことができます.
もう一つの人気ツールEnzyme , しかし、多くはそれが悪いテスト慣行につながることができると信じています.主な関心事は、酵素がコンポーネントの内部動作をテストすることができる余分なユーティリティを提供することです.反応テストでチームが反応;したがって、状態などの反応の機能をテストする必要はありません.componentDidMount , など他のライブラリを使用することもできます.

何をテストするには?
反応におけるコンポーネントテストのとき、フォーカスはユーザがどのように反応コンポーネントと対話するかを模写することにあるべきです.これは、ユーザが何をすべきか、またはテストすべきではないかどうかをテストする必要があることを意味し、実装をテストする代わりに(例えば、検索/入力フィールドの値を変更することができます)、レンダリングするとアプリケーションと対話することを意味しますcomponentDidMount X回数と呼ばれます.
テストを書くとき、自分自身に問い合わせる良い質問がいくつかあります.
  • コンポーネントは何を表しますか?また、異なる条件の下で異なるレンダリング?
  • これは、ユーザーが表示され、潜在的に対話するものです.それについて考えることによって、あなたはまた、ユーザーがアクセスされるべきであるとわかるでしょう
  • ユーザーがコンポーネントと対話するとどうなりますか?
  • これらは、ユーザーがクリックし、アプリケーションには、それらをクリックすると、それらは何かを期待します.テストは、イベントが引き起こされるとき、何が起こるべきであるかを証明するために書かれなければなりません.
  • 関数がプロップとして渡されると、コンポーネントはどのように使用されますか?
  • この関数の動作をJEST MOCKの概念を使用して再現する必要があるかもしれません

  • どのようにテストを書くか?
    だから、面白い部分には、どのようにJESTを使用してコンポーネントを反応テストする.
    RTLの最も使用される関数は以下の通りです:
  • render – コンポーネントのレンダリング
  • cleanup – がマウントされたrender , and
  • fireEvent – クリックのようなイベントを発射する.
  • Jestの最も使用される関数は:
  • expect マッチして
  • jest.fn() 直接機能を偽造する
  • jest.spyOn() オブジェクトメソッドをモックする
  • jest.mock() モジュール全体です.
  • テストは次のように構成します.
  • すべて宣言jest.fn()/spyOn()/mock() 嘲笑された実装の有無にかかわらず
  • RTLの呼び出しrender 引数としてテスト件名を持つ関数-コンポーネントがコンテキストを消費するたびにコンテキストを提供します.また、このRouter Linkがこのコンポーネントで使用されるなら、プロパティラッパーと値MemoryRouter(反応ルータから輸入される)をもつオブジェクトを第2の引数として通過しなければなりません.必要に応じてMemoryRouterタグのコンポーネントをラップします
  • RTLのクエリ関数を使用して、応答DOMツリーを照会します.getByRole() ) を呼び出して値をチェックする
  • 呼び出しによって照らされる値をチェックしてくださいexpect() に沿って適切なマッチ.ユーザ相互作用を複製するfireEvent
  • また、debug() レンダリングを呼び出すメソッド.デバッグは、あなたのテストをデバッグするような状況のために反応木で何が描かれるかチェックすることにとって素晴らしいです.
    以下のコードを検索コンポーネントの例として使用します.
    render = () => {
      const {
        validateSelection,
        minCharacters,
        placeholder,
        inputFluid,
        inputLabel,
        clear
      }: any = this.props
    
      const { isLoading, value, results } = this.state
    
      const icon = validateSelection ? (
        <Icon name="check" color="green" />
      ) : (
        <Icon name="search" />
      )
    
      return (
        <Search
          minCharacters={minCharacters}
          loading={isLoading}
          icon={icon}
          onResultSelect={this.onResultSelect}
          onSearchChange={this.onSearchChange}
          results={results}
          value={clear ? null : value}
          fluid
          placeholder={placeholder}
          input={{ fluid: inputFluid, label: inputLabel }}
        />
      )
    }
    
    上記の小道具や状態を破壊している.また、私たちはSemantic UI React Search モジュールです.本質的には、上記の入力フィールドをレンダリングします.変更されると、それはonSearchChange とセマンティックUIの反応は自動的に2つの引数を渡します.event and data (現在の値を含むすべての小道具).一つonSearchChange ’s仕事はAPIを呼び出して、現在の値と一致する結果を返すことです.
    以下は、このコンポーネントのテストです.
    import '@testing-library/jest-dom/extend-expect'
    import React from 'react'
    import { render, cleanup, fireEvent } from '@testing-library/react'
    import SearchField from './SearchField'
    
    afterEach(cleanup)
    jest.useFakeTimers()
    
    test('<SearchField />', () => {
      const handleResultSelectMock = jest.fn()
      const apiServiceMock = jest
        .fn()
        .mockImplementation(() =>
          Promise.resolve({ entity: { success: true, data: ['hello', 'adios'] } })
        )
    
      const { getByRole, debug } = render(
        <SearchField
          handleResultSelect={handleResultSelectMock}
          apiService={apiServiceMock}
        />
      )
    
      const input = getByRole('textbox')
      expect(apiServiceMock).not.toHaveBeenCalled()
      expect(input).toHaveValue('')
    
      fireEvent.change(input, { target: { value: 'search' } })
      expect(input).toHaveValue('search')
      jest.advanceTimersByTime(600)
    
      expect(apiServiceMock).toHaveBeenCalledWith('search')
      expect(apiServiceMock).toHaveBeenCalledTimes(1)
      debug()
    })
    

    上の例で何が起こっているのですか.
    このコンポーネントをテストするために必要なすべての依存関係をインポートしました.
  • ジェスト詐欺師を拡張する
  • render , cleanup , fireEvent - テストライブラリユーティリティ
  • SearchField - 被検査成分
  • import '@testing-library/jest-dom/extend-expect'
    import React from 'react'
    import { render, cleanup, fireEvent } from '@testing-library/react'
    import SearchField from './SearchField'
    
    我々は、Jestの機能と呼ばれましたafterEach とRTLのメソッドcleanup 議論として.cleanup はRTLのマウントされた全てをアンマウントすることでテスト間のメモリリークがないことを確認しますrender メソッド.また、ジェストのuseFakeTimers 機能モックタイマー機能.
    afterEach(cleanup)
    jest.useFakeTimers()
    
    コンポーネントは、関数でなければならない2つの小道具を必要とします.したがって、コンポーネントにpropsとして渡される2つの関数をmockkingすることから始めました.handleResultSelectMock and apiServiceMock . handleResultSelectMockhandleResultSelect and apiServiceMock to apiService . そしてRTLのrender SearchFieldコンポーネントを引数としてメソッドを呼び出します.
    test('<SearchField />', () => {
      const handleResultSelectMock = jest.fn()
      const apiServiceMock = jest
        .fn()
        .mockImplementation(() =>
          Promise.resolve({ entity: { success: true, data: ['hello', 'adios'] } })
        )
    
      const { getByRole, debug } = render(
        <SearchField
          handleResultSelect={handleResultSelectMock}
          apiService={apiServiceMock}
        />
      )
    })
    

    テストされているコンポーネントがAwrapper: Memory Router またはcontext 成功する.以下の例を見てください.
    const { getByTestId, container } = render(
      <UserContext.Provider value={context}>
        <MainLoggedIn
          config={{
            get: jest.fn().mockImplementation(() => ({
              globalMenu: [{ requiredPermissions: ['Navbar'] }]
            }))
          }}
          history={{ history: ['first_history', 'second_history'] }}
          children={['first_child', 'second_child']}
        />
      </UserContext.Provider>,
      { wrapper: MemoryRouter }
    )
    
    アフターrender が呼び出されると、反応するDOMツリーを問い合わせ、テストしたい要素を見つけます.下記使用getByRole , しかし、RTLは他の多くのクエリセレクタ関数を提供します.
    const input = getByRole('textbox')
    
    値をチェックするには、関数expect いくつかのmatchersの1つに沿って.ここでは、ApiserVicemockが呼び出されていないことを確認し、入力フィールドが空の文字列であることを確認しますvalue = '' ) コンポーネントが最初にレンダリングされるとき.
    expect(apiServiceMock).not.toHaveBeenCalled()
    expect(input).toHaveValue('')
    
    イベントを関数change RTLのfireEvent ユーザーの動作を複製する.このイベントは、'' to 'search' . 他のシナリオを使用して他のシナリオを複製することができますfireEvent メソッドclick() , mouseOver() . ジェストadvanceTimersByTime メソッドは、600 msでmockタイマーを前進させるために呼び出されます.したがって600番号は引数として渡されます.advanceTimersByTime タイマー関数によってキューされて、与えられた時間(この場合の600 ms)以内に実行されるタスクを確実にします.
    fireEvent.change(input, { target: { value: 'search' } })
    expect(input).toHaveValue('search')
    jest.advanceTimersByTime(600)
    
    イベントを発射した後、我々はいくつかのことが起こると期待しているapiServiceMock 関数は一度呼び出され、apiServiceMock 現在の入力値にマッチします.
    expect(apiServiceMock).toHaveBeenCalledWith('search')
    expect(apiServiceMock).toHaveBeenCalledTimes(1)
    debug()
    
    最後にdebug 関数は、反応ツリーで何が表示されているかを調べ、テストをデバッグするのを助ける.

    概要
  • 小さくて簡単なテストはよりよいです.
  • 各コンポーネントを独立してテストします.
  • ユーザーがどのように表示され、どのようにコンポーネントと対話するかをテストします.
  • テストする必要があるものを評価した後、テストを開始します.

  • 話題の詳細
  • Jest cheat sheet
  • Modern React testing, part 3: Jest and React Testing Library
  • The Right Way to Test React Components
  • React Testing Library - Introduction
  • Configuring Jest
  • Mock Functions or Spies Demystified - How Does jest.fn() Work?