Next.js+TypeScript環境でJestを設定してテストをする

公開:2021/02/02
更新:2021/02/03
3 min読了の目安(約3400字TECH技術記事

Next.js+TypeScript環境でのJestを使ったテスト設定です。
テストユーティリティーはreact-domに同封されているものだけを使用します。
DOMイベントに反応するだけの簡単なコンポーネントのテストをしてみます。

インストール

npm install --save-dev jest @types/jest ts-jest

設定

jest.config.json

module.exports = {
    preset: 'ts-jest',
    globals: {
        'ts-jest': {
            tsconfig: '<rootDir>/spec/tsconfig.json'
        }
    }
}

spec/tsconfig.json

{
  "extends": "../tsconfig.json",
  "include": ["./**/*"],
  "compilerOptions": {
    "jsx": "react-jsx"
  }
}

Next.jsのjsxコンパイラオプションはpreserveとなっています。
これをテスト用はreact-jsxに設定します。

react-jsx

React17から入った新しいJSX変換です。

https://ja.reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html

https://zenn.dev/uhyo/articles/react17-new-jsx-transform

TypeScriptは4.1からjsxコンパイラオプション

  • react-jsx
  • react-jsx-dev

の追加で対応されています。

https://devblogs.microsoft.com/typescript/announcing-typescript-4-1/#react-17-jsx-factories

https://github.com/microsoft/TypeScript/pull/39199

これによりいつものimport文が必要なくなりました。
Next.js環境でjsxpreserveであっても、テスト用にBabelを使用しなくても良いのです。

// さよなら
import React from 'react'

イベントのテストとスナップショットテスト

このようなコンポーネントをテストしたいと思います。

import { useState } from 'react'

export const Ababababa = () => {

    const [title, setTitle] = useState<string>('Stay Calm')

    const onMouseEnter = () => { setTitle('あばばばば') }
    const onMouseLeave = () => { setTitle('あばっ') }

    return (
        <h1 {...{onMouseEnter,onMouseLeave}}>
            {title}
        </h1>
    )
}

スナップショットテストにします。

import { render } from 'react-dom'
import { act } from 'react-dom/test-utils'
import { Ababababa } from '../../components/Ababababa'

describe('あばばばば', () => {
    let container: HTMLDivElement
    beforeEach(() => {
        document.body.appendChild(container = document.createElement('div'))
    })
    afterEach(() => {
        document.body.removeChild(container)
    })

    it('should Stay Calm', () => {
        render(<Ababababa />, container)
        expect(container.innerHTML).toMatchSnapshot()
    })

    it('should あばばばば', () => {
        act(() => {
            render(<Ababababa />, container)
        })
        act(() => {
            container.dispatchEvent(new MouseEvent('mouseover', { bubbles: true }))
            container.children[0].dispatchEvent(new MouseEvent('mouseover', { bubbles: true, cancelable: true }))
        })
        expect(container.innerHTML).toMatchSnapshot()
    })

    it('should あばっ', () => {
        act(() => {
            render(<Ababababa />, container)
        })
        act(() => {
            container.children[0].dispatchEvent(new MouseEvent('mouseout', { bubbles: true }))
            container.dispatchEvent(new MouseEvent('mouseout', { bubbles: true }))
        })
        expect(container.innerHTML).toMatchSnapshot()
    })
})

Reactのイベントハンドリングの仕様の関係で onMouseEnter, onMouseLeave, に対して mouseover, mouseout イベントを発火しています。(ここではまりました)

https://ja.reactjs.org/docs/handling-events.html

https://ja.reactjs.org/docs/events.html

テストを実行するとスナップショットが作成されるので確認してみます。

// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`あばばばば should Stay Calm 1`] = `"<h1>Stay Calm</h1>"`;

exports[`あばばばば should あばっ 1`] = `"<h1>あばっ</h1>"`;

exports[`あばばばば should あばばばば 1`] = `"<h1>あばばばば</h1>"`;

期待通りの出力なので、次回のテストからマウスイベントに対する出力がスナップショットと比較され正しいことを確認できます。

Next.jsにおけるテスト

pages以下のコンポーネントはテストが難しくなりがちなので、部品単位で単体テストを作成し、なるべく機能を保証していく形になりそうです。