Vue CLI テスト Tips集

2019/08/17

CaT vol.7 in 秋田 / Short LT

jiyuujin

  • Vue/Nuxt/PHP/Scala/Java/Swift
  • v-kansai/kansai.ts/..
  • Web猫ブログ (webneko.dev)

毎月大阪、京都交互で

初 秋 田

フロントでテストやってますか?

社内向け管理画面が主でした

  • Play(Scala) + Vue = SPA構成
  • bootstrap-vue / vue-i18n / ..
  • 時間、優先度を考えテストはしない😌

とある売上を管理する本部機能を

  • CakePHP + Vue = SPA構成
  • Vue CLI + i18n + Jest
  • プロダクトとしては1年前から、Smartyを使ってたけど

導入部分の話は割愛します🙏

と言いたかったけど。。🥵

初期インストールですんなりイケると思った

  • babel7でコード変換できなかった
  • babel-coreと諸々のバージョンが一致してなかった
  • 通信処理で async - await が使えてなかった

babel.config

module.exports = {
    presets: [
        '@vue/app',
        [
            '@babel/preset-env',
            {
                targets: {
                    browsers: ['> 1%', 'last 2 versions'],
                    node: 'current'
                },
                useBuiltIns: 'entry',
                corejs: 3
            }
        ]
    ],
    plugins: [
        [
            '@babel/plugin-transform-runtime',
            {
                regenerator: true
            }
        ],
        '@babel/plugin-proposal-class-properties',
        '@babel/plugin-proposal-export-default-from',
        '@babel/plugin-syntax-dynamic-import'
    ]
}

Babel周りには気を配りましょう

決して複雑な構成では無かったが

.
├── doc_root
│   ├── app
│   ├── tests
│   └── webroot
├── frontend
│   ├── public
│   ├── src
│   │   ├── api
│   │   ├── assets
│   │   ├── components
│   │   ├── plugins
│   │   ├── services
│   │   ├── types
│   │   ├── utils
│   │   └── views
│   └── tests

丁寧にjest.configを書くこと

では実際にテストを書くぞ

そもそも何をテストしようか🤔

文法はESLint/Prettierに任せて

  • コンポーネントのマウント
  • イベント発火を受け、ステートの変動に矛盾が無いか
  • 激重API対策

お世話になるパッケージですが

  • @vue/test-utils (default)
  • @testing-library/vue
  • axios-mock-adapter

render / fireEvent

// @testing-library/vue
import '@testing-library/jest-dom/extend-expect'
import { render, fireEvent } from '@testing-library/vue'

describe('Button', () => {
    it('click event', async () => {
        const { getByText } = render(Child, {
            propsData: {
                text: '追加する'
            }
        })
        const button = getByText('追加する')
        await fireEvent.click(button)
    })
})

CSS frameworkを入れることも容易に

// @vue/test-utils
import { createLocalVue } from '@vue/test-utils'

// bootstrap-vue
import BootstrapVue from 'bootstrap-vue'

const localVue = createLocalVue()

localVue.use(BootstrapVue)

describe('Button', () => {
    it('click event', async () => {
        const { getByText } = render(Child, {
            localVue,
            propsData: {
                text: '追加する'
            }
        })
        const button = getByText('追加する')
        await fireEvent.click(button)
    })
})

続いて通信処理ですが

事前に通信処理を切り分けておくと楽

import axios from 'axios';
import { Sale } from '@/types';

export default class SaleService {
    public async fetchSummary(
        id: number
    ): Promise<Sale> {
        const res = await axios.get(`/summary?id=${id}`);
        return res.data;
    }
}

serviceにアクセスしてテスト

import SaleService from '@/services/SaleService';

describe('SaleService', () => {
    let saleService: SaleService;

    beforeEach(() => {
        saleService = new SaleService();
    });

    it('fetch data', async () => {
        const responseData = await saleService.fetchSummary(
            null
        );
        expect(responseData.id).toBe(1);
    });
})

異常系を含めたテスト

激重APIだからこそ

正しくレスポンスできているか

あえて遅らせてテストしたり

describe('Service', () => {
    let mockAxios: MockAdapter

    beforeEach(() => {
        // あえてモックの返却を遅らせる
        mockAxios = new MockAdapter(axios, {
            delayResponse: 500
        })
    })

    it('response status === 403', async () => {
        mockAxios.onGet(`${BASE_API}1`)
            .reply(403)

        try {
            await getPostID(1)
        } catch (error) {
            expect(error.message).toEqual('Request failed with status code 403');
        }
    })
})

この辺りを見てテストを書いてます

  • コンポーネントのマウント
  • イベント発火を受け、ステートの変動に矛盾が無いか
  • フロントとして激重API対策

ご清聴ありがとうございました🙇‍♀️