LoginSignup
32
29

More than 5 years have passed since last update.

Vue Test Utilsでユーザー入力のテストの仕方

Posted at

イベントをシミュレーションする

Vueコンポーネントの中でよくやることの1つはユーザーが発生したイベントをハンドルすることです。vue-test-utilsとJestでイベントのテストを書きやすくします。triggerとJestのモック関数を使ってコンポーネントのテストを書いてみましょう。

このガイドのテストのソースコードはこちらです。


この記事はVue Testing Handbookのために書いたものです。Vue Testing Handbookはおーペンソース本です。英語版は90%を書き終わって、日本版は翻訳中です。翻訳したい方と新しいガイドを書きたい方はどうぞ!


コンポーネントを作成する

<input><button>がある簡単な<FormSubmitter>コンポーネントを作ります。ボタンをクリックすると、何かが起こります。最初の例にボタンをクリックすると成功メッセージを表示します。次の例にボタンをクリックするとデータを外部サービスに送信します。

<FormSubmitter>を作って、テンプレートにこう書きます:

<template>
  <div>
    <form @submit.prevent="handleSubmit">
      <input v-model="username" data-username>
      <input type="submit">
    </form>

    <div 
      class="message" 
      v-show="submitted"
    >
      {{ username }}さん、お問い合わせ、ありがとうございます。
    </div>
  </div>
</template>

ユーザーはフォームを送信すると、メッセージを表示させます。フォームを非同期に送信するので、@submit.preventで送信します。そうしないと、デフォルトアクションが走ります。フォームを送信するとデフォルトアクションはページを更新します。

フォームを送信するロジックを追加します:

<script>
  export default {
    name: "FormSubmitter",

    data() {
      return {
        username: '',
        submitted: false
      }
    },

    methods: {
      handleSubmit() {
        this.submitted = true
      }
    }
  }
</script>

簡単です。送信するとsubmittedtrueにするだけです。

テストを書く

テストをこう書きます:

import { shallowMount } from "@vue/test-utils"
import FormSubmitter from "@/components/FormSubmitter.vue"

describe("FormSubmitter", () => {
  it("フォームを更新するとお知らせを表示", () => {
    const wrapper = shallowMount(FormSubmitter)

    wrapper.find("[data-username]").setValue("alice")
    wrapper.find("form").trigger("submit.prevent")

    expect(wrapper.find(".message").text())
      .toBe("aliceさん、お問い合わせ、ありがとうございます。")
  })
})

テストがわかりやすいです。コンポーネントをマウントして、usernamesetValueで入力して、そしてvue-test-utilstriggerを使って送信することシミュレーションします。triggerをカスタムイベントにも使えるのでsubmit.preventmyEvent.doSomethingでも問題ないです。

このテストはユニットテストの3つの改行で分けました:

  1. arrange (初期設定) - テストの準備。この場合、コンポーネントをレンダーします
  2. act (実行) - システムを実行します。
  3. assert (検証)- 期待と検証を比べます。

ステップずつテストを分けるのが好きです。読みやすくなると思います。

yarn test:unitで実行すると、パスするはずです。

triggerの使い方は簡単です。ただイベントを発生させたい要素をfindで検証して、イベント名をtriggerに渡して呼び出します。

実例

アプリにフォームがよくあります。フォームのデータをエンドポイントに送信します。handleSubmitの実装を更新して、axiosというよく使われるHTTPクライエントで送信してみます。そしてそのコードのテストを書きます。

axiosVue.prototype.$httpにエイリアスすることもよくあります。詳しくはこちら。こうすると、this.$http.getを呼び出すだけでデータをエンドポイントに送信できます。

エイリアスして、this.$httpでフォームを送信する実装はこうです。

handleSubmitAsync() {
  return this.$http.get("/api/v1/register", { username: this.username })
    .then(() => {
      // メッセージを表示するなど
    })
    .catch(() => {
      // エラーをハンドル
    })
}

this.$httpをモックしたら、上のコードを簡単にテストできます。モックするにはmocksマウンティングオプションを使えます。mocksついて詳しくはこちらhttp.getのモック実装はこうです。

let url = ''
let data = ''

const mockHttp = {
  get: (_url, _data) => {
    return new Promise((resolve, reject) => {
      url = _url
      data = _data
      resolve()
    })
  }
}

いくつかの面白い点があります。

  • $http.getに渡すurldataを保存するためにurldata変数を作ります。そうすると、handleSubmitAsyncを呼び出すとたたしいエンドポイントと正しいペイロードで動くか検証できます。- urldataをアサインしてから、Promiseをすぐにresolve(解決)します。これは正解となったレスポンスのシミュレーションです。

テストを書く前に、handleSubmitAsyncを更新します:

methods: {
  handleSubmitAsync() {
    return this.$http.get("/api/v1/register", { username: this.username })
      .then(() => {
        this.submitted = true
      })
      .catch((e) => {
        throw Error("Something went wrong", e)
      })
  }
}

そして<template>を更新して、新しいhandleSubmitAsyncを使います:

<template>
  <div>
    <form @submit.prevent="handleSubmitAsync">
      <input v-model="username" data-username>
      <input type="submit">
    </form>

  <!-- ... -->
  </div>
</template>

テスを書きましょう。

AJAXコールをモックする

上に書いてあるモック関数をテストの最初のdescribeブロックの上に追加します。

let url = ''
let data = ''

const mockHttp = {
  get: (_url, _data) => {
    return new Promise((resolve, reject) => {
      url = _url
      data = _data
      resolve()
    })
  }
}

テストを書きましょう。mockHttpmocksに渡して、$httpの代わりに使います。

it("フォームを更新するとお知らせを表示", () => {
  const wrapper = shallowMount(FormSubmitter, {
    mocks: {
      $http: mockHttp
    }
  })

  wrapper.find("[data-username]").setValue("alice")
  wrapper.find("form").trigger("submit.prevent")

  expect(wrapper.find(".message").text())
    .toBe("aliceさん、お問い合わせ、ありがとうございます。")
})

こうすると、Vue.prototype.$httpの本当のAJAXライブラリーを使う代わりに、モックを使います。これがいいことです。テスト環境を簡単に扱います。

yarn test:unitを実行すると、テストが失敗すます。

FAIL  tests/unit/FormSubmitter.spec.js
  ● FormSubmitter › フォームを更新するとお知らせを表示

    [vue-test-utils]: find did not return .message, cannot call text() on empty Wrapper

問題は、mockHttpが返却するPromiseresolveする前にテストの実行が終わりました。asyncをつけるとテストは同期に実行させます。

it("フォームを更新するとお知らせを表示", async () => {
  // ...
})

Promiseをすぐにresolveさせるライブラリーも必要です。よく使うのがflush-promisesです。yarn add flush-promisesでインストールできます。そしてテストを更新します。

import flushPromises from "flush-promises"
// ... 

it("フォームを更新するとお知らせを表示", () => {
  const wrapper = shallowMount(FormSubmitter, {
    mocks: {
      $http: mockHttp
    }
  })

  wrapper.find("[data-username]").setValue("alice")
  wrapper.find("form").trigger("submit.prevent")

  await flushPromises()

  expect(wrapper.find(".message").text())
    .toBe("aliceさん、お問い合わせ、ありがとうございます。")
})

これでテストがパスします。flush-promiseのソースコードが10行だけなので、読んでみて、理解することがおすすめです。

エンドポイントとペイロードが正しいかを検証することもできます。2つの検証をテストに追加します。

// ...
expect(url).toBe("/api/v1/register")
expect(data).toEqual({ username: "alice" })

テストはパスします。

まとめ

このガイドで学んだことは:

  • triggerを使ってイベントを発火させること
  • setValuev-modelを使う<input>の値を設定する
  • ユニットテストを3つのステップに分けること。(初期設定、実行、検証) 
  • Vue.prototypeのメソッドをモックする
  • flush-promisesを使ってPromiseをすぐにresolveさせる

このガイドのテストのソースコードはこちらです。

32
29
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
32
29