# イベントをシミュレーションする
Vueコンポーネントの中でよくやることの1つはユーザーが発生したイベントをハンドルすることです。vue-test-utilsとJestでイベントのテストを書きやすくします。triggerとJestのモック関数を使ってコンポーネントのテストを書いてみましょう。
このガイドのテストのソースコードはこちらです。
# コンポーネントを作成する
<input>と<button>がある簡単な<FormSubmitter>コンポーネントを作ります。ボタンをクリックすると、何かが起こります。最初の例にボタンをクリックすると成功メッセージを表示します。次の例にボタンをクリックするとデータを外部サービスに送信します。
<FormSubmitter>を作って、テンプレートにこう書きます:
<template>
<div>
<form @submit.prevent="handleSubmit">
<input v-model="username" data-username>
<input type="submit">
</form>
<div
class="message"
v-if="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("フォームを更新するとお知らせを表示", async () => {
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させる
このガイドのテストのソースコードはこちらです。
← 算出プロパティ 発生したイベントのテスト →