Jest と Vue Test Utils を使った Vue コンポーネントテスト実装例

こんにちは。
この記事では、JestとVue Test Utilsを使用して、Vue コンポーネントの単体テストを実装する方法をシンプルな例で解説します。

## 1. サンプルコンポーネント (TestComponent.vue)
まず、テスト対象となるシンプルなコンポーネントの例です。
このコンポーネントは、`isEnabled`というプロパティに応じて、ボタンを表示するかどうかを制御しています。

```
<!-- TestComponent.vue -->

<template>
  <div>
    <h2>テストタイトル</h2>
    <p>テスト説明文1</p>
    <p>テスト説明文2</p>
    <!-- isEnabled が false の場合のみボタンを表示 -->
    <TestButton v-if="!isEnabled" />
  </div>
</template>

<script>
import TestButton from './TestButton.vue';

export default {
  name: 'TestComponent',
  props: {
    isEnabled: {
      type: Boolean,
      required: true,
    },
  },
  components: { TestButton },
};
</script>
```

また、シンプルなボタンコンポーネント(TestButton.vue)の例です。
```
<!-- TestButton.vue -->

<template>
  <a :href="buttonHref" data-ga="test_button">
    ボタンテキスト
  </a>
</template>

<script>
export default {
  name: 'TestButton',
  data() {
    return {
      buttonHref: '/test',
    };
  },
};
</script>
```

## 2. Jest テストコード (TestComponent.test.js)
次に、上記コンポーネントに対するJestのテストコード例です。
この例では、コンポーネントの初期表示と、`isEnabled`プロパティの値による表示制御をテストしています。

```
// TestComponent.test.js

// 環境変数とグローバルオブジェクトの設定
process.env.VUE_APP_ENV = 'local';
global.performance = require('perf_hooks').performance;

// コンポーネントとテストツールのインポート
import TestComponent from '@/components/test/TestComponent.vue';
import { mount } from '@vue/test-utils';

// 初期 props の定義（未有効状態：isEnabled が false）
const initialProps = {
  isEnabled: false,
};

let wrapper;

/**
 * createWrapper 関数
 * 渡された props を用いて TestComponent をマウントする
 * @param {Object} argProps - 渡す props オブジェクト。未指定の場合は initialProps を使用
 */
function createWrapper(argProps) {
  const props = argProps ? argProps : initialProps;
  wrapper = mount(TestComponent, {
    propsData: props,
  });
}

// 各テスト後にコンポーネントをアンマウントし、テスト間の影響を防ぐ
afterEach(() => {
  wrapper.unmount();
});

/* =======================================================================
 * テストケースのグルーピング (describe と test/it)
 ======================================================================== */
describe('TestComponent.vue', () => {
  // 非同期テスト: コンポーネントの初期表示確認
  test('初期表示', async () => {
    await createWrapper();
    // タイトルの確認
    const title = wrapper.find('h2');
    expect(title.text()).toBe('テストタイトル');
    
    // 説明文の確認
    const descriptions = wrapper.findAll('p');
    expect(descriptions.at(0).text()).toBe('テスト説明文1');
    expect(descriptions.at(1).text()).toBe('テスト説明文2');
    
    // isEnabled が false の場合は TestButton が表示される
    const buttonComponent = wrapper.findComponent({ name: 'TestButton' });
    expect(buttonComponent.exists()).toBe(true);
    
    // ボタンのリンク属性と data-ga の確認
    const linkButton = wrapper.find('a');
    expect(linkButton.attributes('href')).toBe('/test');
    expect(linkButton.attributes('data-ga')).toBe('test_button');
  });

  // isEnabled の値による表示制御のテスト
  describe('接続状態による表示確認', () => {
    // isEnabled が false の場合
    test('isEnabled が false の場合はボタンが表示される', async () => {
      await createWrapper();
      const buttonComponent = wrapper.findComponent({ name: 'TestButton' });
      expect(buttonComponent.exists()).toBe(true);
    });
    
    // isEnabled が true の場合
    test('isEnabled が true の場合はボタンが表示されない', async () => {
      // 初期 props をディープコピーして isEnabled を true に変更
      const testProps = JSON.parse(JSON.stringify(initialProps));
      testProps.isEnabled = true;
      await createWrapper(testProps);
      const buttonComponent = wrapper.findComponent({ name: 'TestButton' });
      expect(buttonComponent.exists()).toBe(false);
    });
  });
});
```

## 3. テストコードの解説
1. 環境変数とグローバルオブジェクトの設定
`process.env.VUE_APP_ENV`や`global.performance`の設定により、テスト実行時の環境を整えています。

2. コンポーネントとテストツールのインポート
`import { mount } from '@vue/test-utils';`を使用して、コンポーネントのマウントを行います。

3. 初期propsの定義
`initialProps`オブジェクトで、コンポーネントの初期状態（ここでは `isEnabled: false`）を定義しています。

4. コンポーネントマウントの補助関数(createWrapper)
この関数を使うことで、複数のテストケースで同じマウントロジックを再利用し、必要に応じてpropsを上書きできます。

5. afterEach フック
各テストケースの後に`wrapper.unmount()`を実行し、テスト間の状態の干渉を防いでいます。

6. describeとtest/itブロック
`describe`で関連するテストケースをグループ化し、`test`または`it`ブロックで個々のテストを定義しています。

7. 非同期テスト
`async/await`を用いて、コンポーネントのマウントやDOM更新の完了を待ってから検証を行っています。

8. DOM要素の探索とアサーション
`wrapper.find`や`wrapper.findAll`を使って、特定のタグや要素を探し、`text()`や`attributes()`を使ってその内容を検証しています。

9. 子コンポーネントの探索
`wrapper.findComponent({ name: 'TestButton' })`を用いて、TestComponent内に含まれる TestButtonコンポーネントの存在を確認しています。

10. 深いコピー (deep copy)
`JSON.parse(JSON.stringify(initialProps))`を使って、propsのディープコピーを行い、各テストケースでpropsの変更が他に影響しないようにしています。

## 4. まとめ
このサンプルでは、JestとVue Test Utilsを使用して、シンプルなVueコンポーネントのテストを実装する方法を示しました。

この例dは、Jestでのテスト実装の参考になれれば幸いです。