ダイキのアプリ開発ナレッジ

アプリ開発のナレッジを掲載します

Next.js テスト実装方法 基本編勉強

この記事はUdemyの学習メモです
テストに関して解説してくれる教材はなかなか見つからないのでかなり重宝させてもらいました
www.udemy.com

Next.jsで実装するプロジェクトのテストの導入と静的テストの基本形の内容だけ記載しておきます

目次

テスト準備

1. プロジェクト作成手順

https://github.com/GomaGoma676/nextjs-testing/blob/main/README.md
この通りにやるとテストの導入ができます

1-2. 必要 module のインストール

インストールすると以下のようなメッセージが出ます

  1. axios@0.24.0
  2. swr@1.0.1
  3. msw@0.35.0

added 92 packages from 86 contributors and audited 534 packages in 28.619s

96 packages are looking for funding
run `npm fund` for details

found 0 vulnerabilities

上記のパッケージ警告は0件だがWindows(OSに関係ないのかもしれないが)でReactでパッケージインストールしていると結構な数の警告が出ます
警告が出るのは気持ち悪い派の人のために対処法(https://shinseidaiki.hatenablog.com/entry/2021/10/09/204921)はまとめてみましたが、あんまり意味がないようなので警告が出た場合は放置でいいと思います

2-1.
jest-css-modulesはテスト時に不具合を出す可能性のあるCSSのモック化のためのライブラリ

2-2.
windowsユーザーはtouch使えないので右クリックでファイルを作る

2-3.
testPathIgnorePatternsはテストとは関係ないフォルダを無視する記述
"moduleNameMapper"はCSSファイルがプロジェクトに存在した場合はmock化する記述


2-4.
package.jsonのscripts部分はnpm run ○○にコマンドを設定できる部分なので、testを記述してnpm run testでテスト実行できるようにしている
デフォルトでは全テストケースの成功失敗しか出力されない

    • env=jsdom --verboseオプションをつけると各テストケースの成功失敗が出力される

3-1.
windowsユーザーは touch は右クリック作成

3-3.
npm run devを実行するとtsconfig.jsonの内容が自動的に生成されます

3-4.
f:id:shinseidaiki:20211108003113p:plain
apiフォルダは削除

4-2.
初期化コマンドを実行すると設定ファイルが2つ自動生成される

4-3.
これはpagesやcomponentsフォルダ下の実際に使用されているtsxファイルのユーティリティファイルに対してデプロイするときに自動生成されるCSSファイルについて、記載したフォルダのものだけをCSSに自動生成する記述
これを設定しておかないと存在するtailwindのクラスユーティリティすべてがCSSに出力されてファイルサイズが膨大になる

テストの基本形

5.2のところ

Next.jsのテストはテストフォルダ__test__に○○.test.tsxという形のファイルとして保存する
f:id:shinseidaiki:20211108005427p:plain

ファイルの中身としては以下のように書く
○○.test.tsx

import { render, screen } from '@testing-library/react'
import '@testing-library/jest-dom/extend-expect'
import コンポーネント from '../pages/index'

it('テストケース説明', () => {
  render(<コンポーネント />)
  expect(screen.getByText('期待する文字列')).toBeInTheDocument()
})

renderはコンポーネントに変数などがあれば変数に値を代入してHTMLを作り出します
画面としては実際に表示しませんがitの中では仮想的に画面をいじっているのと同じ環境を作り出しています
screenはrenderでレンダリングした画面を差していて、getByTextで画面の中に'期待する文字列'、教材の場合は'Hello Nextjs'があるかどうか探します
toBeInTheDocumentでは探した文字が画面上に表示されていれば正解であると判定します
これが基本形です

npm run test でテストが失敗した場合は原因などの情報を含めて出力される
この失敗のケースでは"期待している文字列"が見つからなかったので、コンポーネントの方に"期待している文字列"をちゃんと実装しなさいと言っています
しかしながら"期待している文字列"もスペルミスなどで間違う可能性があるので真に受けすぎないのも大事です

FAIL __tests__/Home.test.tsx
× Should render hello text (40 ms)

● Should render hello text

TestingLibraryElementError: Unable to find an element with the text: Hello Nextjs2. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.

......

Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 total
Snapshots: 0 total
Time: 3.296 s
Ran all test suites.
npm ERR! Test failed. See above for more details.


また上記のテスト結果だけでは原因を特定できない時にテストのデバッグがscreen.debug()でできます
ただし明示的にscreen.debug()を入れなくてもテスト失敗の時はデフォルトでデバッグ出力は出力されます

it('テストケースの説明', () => {
  render(<コンポーネント />)
  screen.debug()
  expect(screen.getByText('期待している文字列と違う文字列')).toBeInTheDocument()
})

デバックを入れているときのテストが成功する場合のコンソールへの出力例
(テスト失敗の時はデフォルトでデバッグ出力されている模様)
レンダリングされているHTMLの要素を出力させている

console.log
    <body>
      <div>
         本当の文字列
      </div>
    </body>

Static Site Generation 実装パターンのテスト作成

静的なコンテンツをテストするパターン

テストするための画面作り

まずはテストするための画面を2画面作成します
Next.jeではすべての画面で共通して使われるベースの画面components/Layout.tsxを作成して各画面はそれを継承するのが通常の実装になる

Layout.tsx 基本形

interface TITLE {
    title: string
}

const Layout: React.FC<TITLE> = ({ children, title = 'Next.js' }) => {
    return (
        <div></div>
    )
}

export default Layout

Layout.tsx のreturnの中身は Headとheaderとmainやfooterなどで通常構成される
Link href="/"タグはルーティングでSPAとして差分だけを読み込みに行ってくれる
対してa href="/"タグを使うと全量ページを読み込みに行くのでLayout.tsxのヘッダーに使われる画面遷移ではLinkを使うのが一般的
ホーム画面とブログ画面の2画面に遷移できるようにしておく

Layout.tsxのreturn の中身だけ記載

return (
        <div>
            <Head>
                <title>{title}</title>
            </Head>
            <header>
                <nav>
                   <Link href="/">
                      <a data-testid="home-nav" 
                       >Home</a>
                    </Link>
                    <Link href="/blog">
                     <a data-testid="blog-nav" 
                      >Blog</a>
                  </Link>
                </nav>
            </header>
            <main>
                {children}
            </main>
        </div>
    )

使用しているHeadやLinkのインポートはこちら

import Head from 'next/head';
import Link from 'next/link';


次に画面を作ります
ホーム画面となるindex.tsxを編集します
Layout.tsxをベースに作成するので、return 直下をLayoutで囲みます

index.tsx

import Layout from "../components/Layout"

const Home: React.FC = () => {
  return (
    <Layout title="Home">
      <p>ホーム画面です</p>
    </Layout>
  )
}
export default Home

次に2つ目の画面となるblog-page,tsxをpagesフォルダ下に作成します
next.jsはpagesフォルダ直下に格納したファイル名から自動的にルーティングするので、blog-pageという名前を使うと/blogでblog-page,tsxが自動的に参照されるような仕掛けとなっています
blog-page,tsxもindex.tsxと同じようなコードになります

blog-page,tsx

import Layout from '../components/Layout'

const BlogPage:React.FC = () => {
  return (
    <Layout title="Blog">
      <p>ブログページです</p>
    </Layout>
  )
}
export default BlogPage

テスト実装

1画面目のテスト、静的テスト基本形

まずはindex.tsxの静的な画面をテストします
これはテスト基本形のところで学んだ通り__test__フォルダを作成してHome.test.tsxを作成し、以下のような手順でコードを作成していっててテストを実装していく

まずは基本的なテストモジュールのインポートをする

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

次にテストしたい画面モジュールをインポートする

import Home from '../pages/index'

次にテストケースのひな型を書く

describe('テストケースの説明_○○を確認する', () => {
    it('テストケース1_正常系_○○', async () => {
        
    })
    it('テストケース2_異常系_○○', async () => {
        
    })
})

テストケースの中身を書く

it('テストケース_○○', async () => {
   render(<Home />) # テストしたい画面のレンダリング
   expect(screen.getByText('期待する文字列')).toBeInTheDocument() # 検証する内容
})

このようにしていくと完成形は以下のようになる
Home.test.tsx

import { render, screen } from '@testing-library/react'
import '@testing-library/jest-dom/extend-expect'
import Home from '../pages/index'

it('テストケース説明', () => {
  render(<Home />)
  expect(screen.getByText('ホーム画面です')).toBeInTheDocument()
})

これが静的テストの基本形にあたり、Home画面の表示項目が増えたらexpectを増やしていくことで確認したい項目を増やしてテストを作っていく

2画面目のテスト、画面遷移を伴うテスト

次にBlog画面のテストだが、リンクをクリックして画面遷移をする挙動に関してもテストが行えるため画面遷移のテストもここで行うことにする

そのためには画面遷移つまりLinkコンポーネントのテストには別途必要なコンポーネントがあるためインストールする

npm install next-page-tester

では2つ目の画面をテストするテストファイル__tests__/NavBar.test.tsxを作成する

NavBar.test.tsxでは画面上のリンクのクリックを模擬するのでテスターがユーザーイベントをするためのモジュールをインポートします
またそれに伴い各種モジュールもインポートします

NavBar.test.tsx

import useEvent from '@testing-library/user-event'; // テスターが画面上の要素をクリックできるモジュール
import { getPage } from 'next-page-tester';
import { initTestHelpers } from 'next-page-tester'; // 初期化を行う

initTestHelpers()

次にルーティングからページを取得してきてレンダーする処理を施します
今回は画面が遷移するのでコンポーネントを直接レンダリングするのではなくルーティング経由にします
NavBar.test.tsx

const { page } = await getPage({
    route: '/index',
})
render(page)

今回はクリックイベントが発生するのでクリックイベントのコードは以下になります

useEvent.click(screen.getByTestId('実装時のタグに付与したtestId属性の値'))

なおクリックイベントで画面遷移をするにはLinkタグをクリックする必要があり、テスト上ではuseEvent.clickを使用することでクリックできるが、何をクリックするかのキーとなる部分に関してはgetByTestIdを使用しており、実装時の段階でLayout.tsxのLinkタグの中にdata-testidの属性を付与する必要があることは注意が必要
Layout.tsx

<Link href="/blog">
<a data-testid="blog-nav" 
 ...
>Blog</a>
</Link>

※クリックイベントにはタグ属性にtestIdを付与する必要がある


これらを実装した画面遷移を伴うテストであるNavBar.test.tsxテストの完成形はこちら

import { render, screen } from '@testing-library/react'
import '@testing-library/jest-dom/extend-expect'
import userEvent from '@testing-library/user-event'
import { getPage } from 'next-page-tester'
import { initTestHelpers } from 'next-page-tester'

initTestHelpers()

describe('画面遷移を伴うテスト', () => {
  it('テストケース1', async () => {
    const { page } = await getPage({
      route: '/index',
    })
    render(page)

    userEvent.click(screen.getByTestId('blog-nav'))
    expect(await screen.findByText('ブログページです')).toBeInTheDocument()
  })
})