技術メモ

プログラミングとか電子工作とか

create-react-appで作成したプロジェクトのテスト

f:id:ysmn_deus:20210329174147p:plain React周辺は周りの誰にも聞けないし本当に手探りで色々やってます。
いいかげんフロントでもテストを導入しないと意味不明になってきたので、まずはReact自体のテストを学習します。
基本的にはJest公式などを参考にしながら進めています。

環境構築

プロジェクトの作成

この辺はすっ飛ばしても良さそうですが、今回は学習ということで新規にプロジェクトを作成していきます。
いつもはNext.jsを利用していますが、極力シンプルに進めたいので通常のReactプロジェクトを作成していきます。

npx create-react-app react-test-learning --template typescript

とりあえず実行しておきます

cd react-test-learning
yarn start

いつものくるくるReactマークが出ればOKです。

ロジックのテスト

簡単なテスト

まずはReactのテストというよりJavaScriptのテストを行ってみます。
srcディレクトリにsum.tssum.test.tsを追加します。

sum.ts

export const sum = (a: number, b: number): number => {
  return a + b
}

sum.test.ts

import {sum} from "./sum"

test("add 1 + 2 to equal 3", () => {
  expect(sum(1,2)).toBe(3)
})

これでとりあえずテストは回る筈です。
yarn testでJestを実行します。

 PASS  src/sum.test.ts
  √ add 1 + 2 to equal 3 (2 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        1.888 s, estimated 4 s
Ran all test suites related to changed files.

とりあえず良さそうです。
クリーンなプロジェクトだとTypeScriptを利用しているのでBabelの設定などいるんでしょうが、create-react-appを利用してプロジェクトを作成しているのでその辺はよしなにしてくれているっぽいです。

ちゃんとしたテスト入門であればexpectMatcherを色々見ていくところですが、その辺はやりながら覚えることにします。
基本に立ち返る際にはJestの公式を参照したいところです。

jestjs.io

非同期のテスト

非同期のコードが含まれているテストにはテストに渡す関数の引数に与えられるdoneを使うかPromiseで処理するかのどちらかだそうです。
おそらくナウいのはPromiseだと思いますし、今は async/await を多用すると思うのでその辺で試してみます。
めんどくさいので先ほどのsum.tsに追記します。

sum.ts

~

export const promiseFunction = (value: number) => {
  return new Promise((resolve)=>{
    setTimeout(() => {
      resolve(value)
    }, 1000)
  })
}

sum.test.ts

import {sum, promiseFunction} from "./sum"

~

test("async test", async () => {
  const value = await promiseFunction(100)
  expect(value).toBe(100)
})

Jestを実行します。

 PASS  src/sum.test.ts
  √ add 1 + 2 to equal 3 (1 ms)
  √ async test (1000 ms)

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        3.8 s
Ran all test suites related to changed files.

ちゃんとasyncの方はsetTimeoutで設定した1000msかかってます。

describeブロックとかスコープの話

簡単なテストであれば上記までのtestで処理出来そうですが、もう少し大がかりなテストになってくるとブロック化してテストを回したくなるはずです。(例えば、どこかに通信して取得したデータに対して、それぞれ処理を行うモジュールなど)
テストファイルのグローバルにbeforeEachbeforeAllなどを記載することも出来ますが、describeブロックに分けて実行する方がシンプルだと思います。
セットアップとティアダウンに関しては公式の情報が参考になると思います。

セットアップと破棄 · Jest

testとit

ウェブ上ではitを利用しているテストもありましたが、testはitのエイリアスなので全く同じモノのようです。
ただ公式ドキュメントでもitを利用してテストを書いているケースはないので可読性?からかtestを利用する方がよさそうです。

Reactのテスト

スナップショットテスト

JavaScript(TypeScript)のテストは上記までのもので大丈夫そうではある(モックとかあるけどその辺は後回し)ので、Reactのテストを行っていきます。
まずはレンダリングがうまくいってるか分かりやすいスナップショットテストを行ってみます。
create-react-appでプロジェクトを作成していると、App.tsxApp.test.tsxが自動で作られているのでコレを使い回します。

テストするコードはJestの公式を参考に、react-test-rendererを使用しない形でスナップショットを取っています。

App.tsx

import React, {useState} from 'react';
import logo from './logo.svg';
import './App.css';

type Status = "normal" | "hovered"

const App = () => {
  const [status, setStatus] = useState<Status>("normal")

  const onMouseEnter = () => {
    setStatus("hovered")
  }

  const onMouseLeave = () => {
    setStatus("normal")
  }

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.tsx</code> and save to reload.
        </p>
        <a
          className={status}
          href="https://reactjs.org"
          onMouseEnter={onMouseEnter}
          onMouseLeave={onMouseLeave}
        >
          Learn React
        </a>
      </header>
    </div>
  )
}

export default App

App.test.tsx

import React from 'react'
import { render, screen, fireEvent } from '@testing-library/react'
import App from './App'

test('changes the class when hovered', () => {
  const testComponent = render(<App />)
  expect(testComponent).toMatchSnapshot()
  fireEvent.mouseEnter(screen.getByRole("link"))
  expect(testComponent).toMatchSnapshot()
});

Jestを実行すると、srcディレクトリ下に__snapshots__ディレクトリが作成され、スナップショットが保存されていると思います。

とはいえ、この辺は個人的にはStorybookのVisual Testingで良い気がするのでたぶん使わないと思います。

レンダリングテスト

データなどを受信したり非同期処理を行った後に正しくレンダリングされているかテストします。
とはいえこの辺は実際に作って見て考える要素が大きいので、ここではTesting Libraryの公式例を挙げておくにとどめておきます。

testing-library.com

基本的にはロジックのテストと同様に行いますが、下記に注意しながらテストを組み立てていくようです。

  • 検証したい要素をTesting LibraryのscreenのメソッドのgetByTextgetByRoleなどで取得して評価する
  • イベントの発火はTesting LibraryのfireEventを利用して発火させる
  • イベントの発火の影響を待つ場合はTesting LibraryのwaitForを利用してawaitで待つ

という感じのようです。
もしテストに慣れて記事に出来る程度に知識がついたら別の記事にするかも知れません。