🦤

onAuthStateChange を使うナビゲーションガードのテストの書き方

2023/06/29に公開

ロンラン株式会社 CEO 兼 CTO の武部です。

Vue や React + Firebase Authentication において、router のナビゲーションガード中の onAuthStateChange を利用して認証状態を判定することがあります。

ロンランの製品にも、次のようなナビゲーションガードがあります。

// filename: authenticateNaviGuard.ts
import type { RouteLocationNormalized, NavigationGuardNext } from 'vue-router'

import { onAuthStateChanged } from 'firebase/auth'

import { getAuthWithTenancy } from '@/composables/auth' // これは自作モジュール。getAuth のマルチテナント対応版

/**
 * ログインが必須なページへのルーティング時に実行されるナビゲーションガード。
 * ログインしていない場合は、ログインページにリダイレクトする。
 */
export default (to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext) => {
  onAuthStateChanged(getAuthWithTenancy(), (firebaseUser) => {
    if (!firebaseUser) {
      next('/session/login')
    } else {
      next()
    }
  })
}

vue-router のルート設定では次のように、beforeEnter でこのガードを指定しています。

import authenticateNaviGuard from '@/router/guards/authenticateNaviGuard'

  ...
  {
    path: 'config-account',
    name: 'config-account',
    beforeEnter: authenticateNaviGuard,
    component: () => import('@/pages/ConfigAccount.vue'),
  },
  ...

割とよくある、ルーティング時の認証状態の判定処理ですね。

しかし、テストの書き方が分からなくてちょっと困っていました。

ネットで探してもせいぜい Stack Overflow ぐらいしか出てこない。しかしそのコードも、なんか複雑過ぎてコレ違う感...

やりたいこととしてはシンプルで、条件によって next() がそれぞれ期待どおりに実行されるかをブラックボックステストしたいだけです。

ふと ChatGPT に聞いてみるかーと思い立ち、会話を始めました。

ChatGPT とのペアプログラミング

最近は弊社でも ChatGPT をかなり製品開発に活用しています。とりわけ、Prompt Enginnering Guide を学んだ後からは、ChatGPT との壁打ちの質がかなり向上したと感じています。

まずは、gpt-3.5-turbo に聞いてみました。

これだ!いきなりイメージしていたコードに近いものが出てきました。

コードを読み込んでゆくと、toHaveBeenCalledWith() と書くべきところが toHaveBeenCalled() になっているなど、テストとして不正確だった箇所があったものの、ほぼ期待とおりのコードです。

次に gpt-4 版にも聞いてみます。すると、上記のコードミスもない、さらによいコードが生成されました。ただし残念ながら gpt-4 版には、ハルシネーションがわずかにありました。

生成されたコードのポイントを踏まえて、自分が普段書いているスタイルに整え、次のコードで完成としました。

import { onAuthStateChanged } from 'firebase/auth'

import authenticateNaviGuard from '../authenticateNaviGuard'

import type { User } from 'firebase/auth'

let firebaseUser: User | undefined

const toMock = vi.fn()
const fromMock = vi.fn()
const nextMock = vi.fn()

// Firebase Authのモック
vi.mock('firebase/auth', () => ({
  onAuthStateChanged: vi.fn((auth, callback) => {
    callback(firebaseUser)
  }),
  getAuth: vi.fn(() => ({ currentUser: { user: '1spf23e108zA' } })),
}))

describe('authenticate navigation guard', () => {
  test('should redirect to login page if user is not authenticated', () => {
    // arrange
    firebaseUser = undefined // ログインしていない状態

    // act
    authenticateNaviGuard(toMock, fromMock, nextMock)

    // assert: onAuthStateChangedが呼び出されることを確認
    expect(onAuthStateChanged).toHaveBeenCalled()

    // assert: ログインページへのリダイレクトが行われることを確認
    expect(nextMock).toHaveBeenCalledWith('/session/login')
  })

  test('should proceed to the next route if user is authenticated', () => {
    // arrange
    firebaseUser = {} as User // ログイン済みの状態

    // act
    authenticateNaviGuard(toMock, fromMock, nextMock)

    // assert: onAuthStateChangedが呼び出されることを確認
    expect(onAuthStateChanged).toHaveBeenCalled()

    // assert: 次のルートに進むことを確認
    expect(nextMock).toHaveBeenCalledWith()
  })
})

まとめ

ChatGPT、テストコードの書き方についても詳しい。すごい。

コードレビューは必須なため、依然として人のプログラマは介在するとしても、そう遠くない未来に、Chat GPT に直接 Pull Request を作成してもらう日が来そうですね。

環境情報

  • vue 3
  • vue-router 4
  • vitest 0.32.2
  • fiebase 9.23.0
ロンラン Tech Zenn

Discussion