
はしがき
Jest
で画面遷移のテストをしたくなったのですが、ドンピシャな記事がなかなか出てこなくてしんどかったので、ちょっと調べてサンプルを組んでみました。
フロントエンドのテストコードに取り組んで1日目なので、変なところがあるかもしれませんがご容赦ください。
目次
前提
Vue CLIで生成したプロジェクトを使っています。
Vue3
、TypeScript
、Jest
の3つが満たせていればよいので、それ以外の値は適当です。
Vue CLIの実行
$ vue create test-project
Vue CLI v4.5.13
? Please pick a preset: Manually select features
? Check the features needed for your project: Choose Vue version, TS, Router, Vuex, Linter, Unit
? Choose a version of Vue.js that you want to start the project with 3.x
? Use class-style component syntax? Yes
? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? Yes
? Use history mode for router? (Requires proper server setup for index fallback in production) Yes
? Pick a linter / formatter config: Prettier
? Pick additional lint features: Lint on save
? Pick a unit testing solution: Jest
? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files
? Save this as a preset for future projects? Yes
? Save preset as:
プロジェクト構成(特に手は加えないです)
(root)
├── router
│ └── index.ts
├── views
│ ├── About.vue
│ └── Home.vue
├── App.vue
└── tests
└── unit
└── example.spec.ts
コード
完成系は以下の通りです。変更したファイルだけ載せています。
<template>
<router-link to="/about">Aboutヘ</router-link>
</template>
<template>
<router-view />
</template>
<style>
# app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
</style>
import{ mount, flushPromises } from "@vue/test-utils";
import router from "@/router";
import Home from "@/views/Home.vue";
import About from "@/views/About.vue";
import { defineComponent } from "@vue/runtime-dom";
// 確認用の各画面のWrapper
const homeWrapper = mount(Home, {
global: {
plugins: [router],
},
});
const aboutWrapper = mount(About);
describe("HelloWorld.vue", () => {
it("Home.vueからAbout.vueへの遷移", async () => {
// wrapperにHome.vueを表示させる(初期位置の設定)
router.push("/");
await router.isReady();
// 実際にテストで動かす画面を作成
const testApp = defineComponent({
template: `<router-view />`,
})
const wrapper = mount(testApp, {
global: {
plugins: [router],
},
});
// 遷移前の画面のHTMLがHome.vueと同一か確認
expect(wrapper.html()).toBe(homeWrapper.html());
// <a>タグ(今回は<router-link>しかない)を取得し、押下処理
await wrapper.find("[data-test='router']").trigger("click");
await flushPromises();
// 遷移後の画面のHTMLがAbout.vueと同一か確認
expect(wrapper.html()).toBe(aboutWrapper.html());
});
});
コード解説
公式ドキュメントのTesting Vue Routerに少し手を加えた程度のものになります。
example.spec.ts
がメインなので、順に説明します。
Home.vue
、About.vue
のWrapperを作成
実際に画面を動かすWrapperはテストの中で定義しますが、それとは別に静的に画面の情報(=HTML)を取得したいので、Home.vue
とAbout.vue
のWrapperを事前に作成しておきます。
// 確認用の各画面のWrapper
const homeWrapper = mount(Home, {
global: {
plugins: [router],
}
});
const aboutWrapper = mount(About);
VueWrapperの作成については公式ドキュメントを参照してください。
Home.vue
では<router-link>
を使っているので、<router-link>
を<a>
タグに展開させるため、router
をglobal.plugin
マウントオプションで読み込む必要があります。
具体的に言うと以下の部分ですね。
plugins: [router], // ここでrouter/index.jsで定義されているRouterを読み込みます
RouterLinkStub
を使う手もあるのですが、RouterLinkStub
を使用した場合は<router-link>
のまま出力されるため、今回はやりたいことに沿っておらず使わないことにしました。
(リンク先のテストなどではto
を使うことでコードを短くできるはずです)
routerを活性化
今回はrouter
で画面遷移を行うので、router
が使えるようにしないといけません。
ただそのままだと使えないようなので、router
の初期位置を指定したうえで、router.isReady()
を使ってrouter
が使える状態になったか確認する必要があります。
参考:https://next.vue-test-utils.vuejs.org/guide/advanced/vue-router.html#using-a-real-router
Although it's not entirely clear from the warning, it's related to the fact that Vue Router 4 handles routing asynchronously.
今回はHome.vue
からAbout.vue
への遷移なので、Home.vue(=/)
を初期位置に設定します。
// wrapperにHome.vueを表示させる(初期位置の設定)
router.push("/");
await router.isReady();
実際に動作させるWrapperを作成
router
の準備ができたところで、動かすためのWrapperを作成します。
今回はテスト用に<router-view />
だけ記載されているAppを作成して使うことにしました。
const testApp = defineComponent({
template: `<router-view />`,
})
const wrapper = mount(testApp, {
global: {
plugins: [router],
},
});
画面遷移すると、この<router-view />
が更新される感じですね。
画面遷移のテストを実施
これでテストの準備ができたので、実際にテストをしていきます。
<router-link>
を押下した際に、Home.vue
からAbout.vue
に画面が切り替わっている状態を想定しているため、それの確認を行います。
// 遷移前の画面のHTMLがHome.vueと同一か確認
expect(wrapper.html()).toBe(homeWrapper.html());
// <a>タグ(今回は<router-link>しかない)を取得し、押下処理
await wrapper.find("[data-test='router']").trigger("click");
await flushPromises();
// 遷移後の画面のHTMLがAbout.vueと同一か確認
expect(wrapper.html()).toBe(aboutWrapper.html());
});
wrapper.html()
がawait wrapper.find("[data-test='router']").trigger("click");
の前後で変わるため、それを比較する感じですね。
flushPromises
をしないとrouter
の遷移が完了する前に次に行ってしまいます。
参考:https://next.vue-test-utils.vuejs.org/guide/advanced/vue-router.html#using-a-real-
Again, due to Vue Router 4's new asynchronous nature, we need to await the routing to complete before making any assertions.
In this case, however, there is no hasNavigated hook we can await on. One alternative is to use the flushPromises function exported from Vue Test Utils:
まとめ
とりあえずJest
で画面遷移が確認できることが確認できたので良かったです。
わかってしまえば単純なのですが、前提知識がないためかrouter
周りで苦労しました。
遷移先のパスが同一か、という記事は結構出てきたのですが、実ページまで確認するようなものはあまり見当たらなかったのですが、Jest
で非推奨なのでしょうか?
サンプルはGithubに置いています。
https://github.com/kanade-nishikawa/vue-transition-test-sample
最後までありがとうございました。