イベントをシミュレーションする
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>
簡単です。送信するとsubmitted
をtrue
にするだけです。
テストを書く
テストをこう書きます:
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さん、お問い合わせ、ありがとうございます。")
})
})
テストがわかりやすいです。コンポーネントをマウントして、username
をsetValue
で入力して、そしてvue-test-utils
のtrigger
を使って送信することシミュレーションします。trigger
をカスタムイベントにも使えるのでsubmit.prevent
やmyEvent.doSomething
でも問題ないです。
このテストはユニットテストの3つの改行で分けました:
-
arrange
(初期設定) - テストの準備。この場合、コンポーネントをレンダーします -
act
(実行) - システムを実行します。 -
assert
(検証)- 期待と検証を比べます。
ステップずつテストを分けるのが好きです。読みやすくなると思います。
yarn test:unit
で実行すると、パスするはずです。
trigger
の使い方は簡単です。ただイベントを発生させたい要素をfind
で検証して、イベント名をtrigger
に渡して呼び出します。
実例
アプリにフォームがよくあります。フォームのデータをエンドポイントに送信します。handleSubmit
の実装を更新して、axios
というよく使われるHTTPクライエントで送信してみます。そしてそのコードのテストを書きます。
axios
をVue.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
に渡すurl
とdata
を保存するためにurl
とdata
変数を作ります。そうすると、handleSubmitAsync
を呼び出すとたたしいエンドポイントと正しいペイロードで動くか検証できます。-url
とdata
をアサインしてから、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()
})
}
}
テストを書きましょう。mockHttp
をmocks
に渡して、$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
が返却するPromise
がresolve
する前にテストの実行が終わりました。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
を使ってイベントを発火させること -
setValue
でv-model
を使う<input>
の値を設定する - ユニットテストを3つのステップに分けること。(初期設定、実行、検証)
-
Vue.prototype
のメソッドをモックする -
flush-promises
を使ってPromise
をすぐにresolve
させる
このガイドのテストのソースコードはこちらです。