テストと一緒にVueのアプリを書いてみます。レポジトリをzunda/clean-urlで、稼働例をclean-url.zunda.ninjaで参照できます。

雛形のアプリを作る

新しいディレクトリにVueのアプリを作ります。 手元のXubuntu 24.01にはyarnパッケージとしてyarn 1.22.15が入っています。

$ mkdir clean-url
$ cd clean-url
$ yarn init
$ yarn add -D @vue/cli
$ yarn run vue create .

package.jsonを眺めると、yarn serveで開発用のサーバを起動できそうです。

{
  "scripts": {
    "serve": "vue-cli-service serve"
  }
}

サーバが起動しました。念のためブラウザで閲覧できるのを確認します。

$ yarn serve

(中略)

  App running at:
  - Local:   http://localhost:8080/
  - Network: http://192.168.1.141:8080/

  Note that the development build is not optimized.
  To create a production build, run yarn build.

ユニットテストを書いて実行する

今回は、入力されたURLを加工するアプリを書いてみます。

まず、URLをパーズするヘルパ関数を書きます。 src/helpers.jsに、とりあえず最小の機能を持った下記の関数を定義します。

export function parseUrl(url) {
  return URL.parse(url)
}

VueによるTestingガイドによると、テストフレームワークにはVitestが推奨のようです。インストールします。

$ yarn add -D vitest

Getting Startedガイドに従って、 package.jsontestスクリプトを追加します。

{
  "scripts": {
    "test": "vitest"
  }
}

さらに、src/helpers.spec.jsにユニットテストを書きます。

import { describe, expect, test } from 'vitest'
import { parseUrl } from './helpers'

describe('parseUrl', () => {
  test('parses a minimal URL', () => {
    const parsed = parseUrl('https://example.com')
    const expected = { host: 'example.com', protocol: 'https:' }
    Object.entries(expected).forEach(([prop, goal]) => {
      expect(parsed[prop]).toBe(goal)
    })
  })
})

ユニットテストを実行します。

$ yarn test
yarn run v1.22.15
$ vitest

 DEV  v4.0.18 /home/zunda/c/src/local/clean-url

 ✓ src/helpers.spec.js (1 test) 3ms
   ✓ parseUrl (1)
     ✓ parses a minimal URL 1ms

 Test Files  1 passed (1)
      Tests  1 passed (1)
   Start at  15:11:03
   Duration  190ms (transform 29ms, setup 0ms, import 44ms, tests 3ms, environment 0ms)

 PASS  Waiting for file changes...
       press h to show help, press q to quit

成功しました。

Vueコンポーネントのふるまいをテストする

VitestのComponent Testingガイドによると、Vueのテストには@testing-library/vueを使うのが一般的なようです。Testing LibraryのQuickstartに従ってテストを進めてみます。

ここでは、src/components/UrlCleaner.vueに定義した、UrlCleanerComponentをテストしてみます。1つ目のinputエレメントに任意のURLを入力すると、2つ目の読み取り専用のinputエレメントにv=…以外のクエリパラメータが削除されたURLが出力されるはずです。

この段階では、src/UrlCleaner.jsからUrlCleanerクラスがエキスポートされて、このクラスにはparse()スタティックメソッドとremoveQueriesExceptFor()メソッドが定義されていました。

<template>
  <div class="urlCleaner">
    <input v-model="dirtyUrl"  placeholder="Dirty URL" type="url" />
    <br/>
    <input v-model="cleanUrl" placeholder="Clean URL" readonly="readonly" />
  </div>
</template>

<script>
import { UrlCleaner } from '../UrlCleaner'

export default {
  name: 'UrlCleanerComponent',
  data() {
    return {
      dirtyUrl: ""
    }
  },
  computed: {
    cleanUrl() {
      const x = UrlCleaner.parse(this.dirtyUrl)
      return x ? x.removeQueriesExceptFor(["v"]).toString() : ""
    }
  }
}
</script>

テストでVueコンポーネントをimportするためには、@vitejs/plugin-vueをインスールして有効にする必要があるようです1。NodeからでブラウザのAPIを利用できるようにするため、jsdomもインストールします2

$ yarn add --dev @testing-library/vue @vitejs/plugin-vue jsdom

vite.config.jsとして下記のファイルを作成します。

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
  test: {
    include: [
      './src/**/*.spec.js'
    ],
    environment: 'jsdom'
  }
})

src/tests/UrlCleaner.spec.jsにテストを書き始めます。Testing Libraryによる例では、await fireEvent.update(エレメント, 文字列)で入力するようです。

import { describe, expect, test } from 'vitest'
import { render, cleanup, fireEvent, screen } from '@testing-library/vue'
import UrlCleanerComponent from '../components/UrlCleaner.vue'

describe('UrlCleanerComponent', () => {
  test('removes queries from a URL', async () => {
    render(UrlCleanerComponent)
    const dirty = screen.getByPlaceholderText('Dirty URL')
    const clean = screen.getByPlaceholderText('Clean URL')

    const dirtyUrl = 'http://example.com/path/?p=parameter&v=keep'
    const cleanUrl = 'http://example.com/path/?v=keep'

    await fireEvent.update(dirty, dirtyUrl)

    expect(dirty.value).toBe(dirtyUrl)
    expect(clean.value).toBe(cleanUrl)

    cleanup()
  })
})

--runオプションを付けて実行するとvitestは一度だけの実行で停止してくれるようです。テストが通りました。

$ yarn test --run src/tests/UrlCleaner.spec.js
yarn run v1.22.15
$ vitest --run src/tests/UrlCleaner.spec.js

 RUN  v4.0.18 /home/zunda/c/src/local/clean-url

 ✓ src/tests/UrlCleaner.spec.js (1 test) 36ms
   ✓ UrlCleanerComponent (1)
     ✓ removes queries from a URL 34ms

 Test Files  1 passed (1)
      Tests  1 passed (1)
   Start at  11:31:41
   Duration  1.13s (transform 95ms, setup 0ms, import 300ms, tests 36ms, environment 631ms)

Done in 1.65s.

Yarnを新しいものにする

コードを書き続けていると、 手元のyarn 1.22.15ではnode_modulesディレクトリ以下にインストールされるモジュールのバージョンが安定せず、 yarn installの繰り返しによって時折コマンドが失敗することがわかりました。

Yarn 4+のページに従ってyarnコマンドを4.12.0に更新したところ、 yarn testコマンドの実行によってインストールされているモジュールのテストも走るようになりました。 vite.config.jsで定義したdefineConfig関数の引数のtestプロパティにincludeプロパティとして['./src/**/*.spec.js']を追加することで、 srcディレクトリ以下のテストのみを走らせることができました。