FullStackOpen Part5-c Testing React apps メモ

Reactアプリのテストツールとして、テスト用にコンポーネントをレンダーするreact-testing-libraryjest-domを使用する

npm install --save-dev @testing-library/react @testing-library/jest-dom

Rendering the component for tests

テストはこんな感じで記述
./components/Note.test.js

renderで描画し、screen.getByTextなどでアクセスする

import React from 'react'
import '@testing-library/jest-dom/extend-expect'
import { render, screen } from '@testing-library/react'
import Note from './Note'

test('renders content', () => {
  const note = {
    content: 'Component testing is done with react-testing-library',
    important: true
  }

  render(<Note note={note} />)

  const element = screen.getByText('Component testing is done with react-testing-library')
  expect(element).toBeDefined()
})

Running tests

テストを走らせるには以下のコマンド

$env:CI=$true; npm test

Test file location

テスト対象のコンポーネントと同じフォルダに置く方法と、tests用フォルダを作ってそこにまとめておく方法の二通がよく用いられる

Searching for content in a component

テスト対象を探す方法はいくつかある
テキストで探す: screen.getByText()

CSSセレクタで探す :
container.querySelector()

import React from 'react'
import '@testing-library/jest-dom/extend-expect'
import { render, screen } from '@testing-library/react'
import Note from './Note'

test('renders content', () => {
  const note = {
    content: 'Component testing is done with react-testing-library',
    important: true
  }


  const { container } = render(<Note note={note} />)


  const div = container.querySelector('.note')
  expect(div).toHaveTextContent(
    'Component testing is done with react-testing-library'
  )
})

テスト用にIDを割り当てて探す: getByTestId

Debugging tests

screen.debug()を使ってHTMLを確認できる
screen.debug(element)とするとそのエレメントのみ抽出して確認できる

Clicking buttons in tests

user-eventを使ってクリックのテストを行う

npm install --save-dev @testing-library/user-event

モック関数を作成し、ボタンをクリックしたら一回だけモック関数が呼ばれていることを確認する

import React from 'react'
import '@testing-library/jest-dom/extend-expect'
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'import Note from './Note'

// ...

test('clicking the button calls event handler once', async () => {
  const note = {
    content: 'Component testing is done with react-testing-library',
    important: true
  }

  const mockHandler = jest.fn()

  render(
    <Note note={note} toggleImportance={mockHandler} />
  )

  const user = userEvent.setup()
  const button = screen.getByText('make not important')
  await user.click(button)

  expect(mockHandler.mock.calls).toHaveLength(1)
})

Tests for the Togglable component

Togglableの場合は以下のようにテストを作成(Togglable.test.js)
buttonを押してトグルできるかチェック
 - toHaveStyle関数を使ってdisplay: noneになっているか確認している

import React from 'react'
import '@testing-library/jest-dom/extend-expect'
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import Togglable from './Togglable'

describe('<Togglable />', () => {
    let container

    beforeEach(() => {
        container = render(
            <Togglable buttonLabel='show...'>
                <div className='testDiv' >
                    togglable content
                </div>
            </Togglable>
        ).container
    })

    test('renders its children', async () => {
        await screen.findAllByText('togglable content')
    })

    test('at the start the children are not displayed', () => {
        const div = container.querySelector('.togglableContent')
        expect(div).toHaveStyle('display: none')
    })

    test('after clicking the button, children are displayed', async () => {
        const user = userEvent.setup()
        const button = screen.getByText('show...')
        await user.click(button)

        const div = container.querySelector('.togglableContent')
        expect(div).not.toHaveStyle('display: none')
    })

    test('toggled content can be closed', async () => {
        const user = userEvent.setup()
        const button = screen.getByText('show...')
        await user.click(button)

        const closeButton = screen.getByText('Cancel')
        await user.click(closeButton)

        const div = container.querySelector('.togglableContent')
        expect(div).toHaveStyle('display: none')
    })
})

Testing the forms

フォームの入力テストもuser-eventを使用する
getByRole('textbox')でinputフィールドを見つけてくる
user.type(input, 'wowbar')で入力

//NoteForm.test.js

import React from 'react'
import { render, screen } from '@testing-library/react'
import '@testing-library/jest-dom/extend-expect'
import NoteForm from './NoteForm'
import userEvent from '@testing-library/user-event'

test('<NoteForm /> updates parent state and calls onSubmit', async () => {
  const createNote = jest.fn()
  const user = userEvent.setup()

  render(<NoteForm createNote={createNote} />)

  const input = screen.getByRole('textbox')
  const sendButton = screen.getByText('save')

  await user.type(input, 'testing a form...')
  await user.click(sendButton)

  expect(createNote.mock.calls).toHaveLength(1)
  expect(createNote.mock.calls[0][0].content).toBe('testing a form...')
})

About finding the elements

例えば複数のInputがフォーム中にある場合、getByRoleだとエラーを返してしまう。
その時に使える方法は: 
-getAllByRoleで複数のInputを配列としてとる方法
-getByPlaceholderTextで検索してとる方法
-querySelectorを使用してCSSセレクタを使って取ってくる方法

Test coverage

テスト実施範囲のレポート以下のコマンドで作成できる

npm test -- --coverage

生成したレポートはプロジェクトの./coverage/lcov-report/index.htmlから見ることができる

Frontend integration tests

ユニットテスト(コンポーネントごとの機能検証)だけでは複雑なシナリオの検証はできない。
そのためEnd to endな検証をして正常に動作するか確認する

Snapshot Testing

Snapshot testingはJestのテスト方法の一つであり、コンポーネントにある変更を加えたときに、それによって生じたHTMLコードの変化を監視し、それがバグによるものなのか、それとも意図したものなのかをトラッキングするもの


いいなと思ったら応援しよう!