Vue Test Utils のチュートリアルで手を動かす
はじめに
jestのチュートリアルが終わったので、次のステップとしてVueのユニットテストを書けるようになりたいと思います。
インストール
テストランナを選ぶ
- テストを実行するプログラムであるテストランナを選ぶ
- vue-test-utilsはどれでも動作する。テストランナにとらわれない
- jest
- mocha-webpack
- webpack+Mochaのラッパ
- vue-loaderを使って、完全なSFCサポートが得られる
- 多くの設定が必要
ブラウザ環境
- vue-test-utilsはブラウザ環境に依存する
- どういうこと?
- 実際のブラウザで実行することもできる(らしい)
- でもおすすめはJSDOMを使用して仮想ブラウザ環境でNode.jsでテストすること
- jestならJSDOMは自動で設定される
- どういうこと?
単一ファイルコンポーネントをテストする
Jestを使用したSFCのテスト
Jestのセットアップ
# まずはvueのプロジェクトを作る $ vue create vtu_sample $ cd vtu_sample $ npm run serve # で動くことを確認 # ライブラリいれる $ npm install --save-dev jest @vue/test-utils # あー、vueのバージョンを2.6xで入れたから、エラーになるな。。 # @vue/test-utilsがv2になっているので、v1を入れる必要がありそう。 $ npm info @vue/test-utils versions # '1.3.0'が1系の最新 $ npm i -D jest @vue/test-utils@1.3.0 # インストール成功
//package.jsonのscript以下追記 "test" : "jest"
jestに*.vue
ファイルの処理方法を教えるために、vue-jset
プリプロセッサを入れる。
$ npm i -D vue-jest
package.jsonにjestの設定を記載。(jest.config.jsonじゃなくてpacakge.jsonに追記する書き方か)
"jest": { "moduleFileExtensions" : [ "js", "json", "vue" ], "transform": { ".*\\.(vue)$": "vue-jest" } // @を/srcのエイリアスにしたい場合の設定 // jsconfig.jsonのpathsで設定しているやつ。 // webpackなら、webpack.config.jsのresolve.aliasに`"@" : path.resolve(__dirname,"src")`と設置するぽい // TSなら,tsconfig.jsonのcompilerOptions.pathsに` { "@/*" : ["src/*"]}` "moduleNameMapper": { "^@/(.*)$": "<rootDir>/src/$1" } }
jestのためのBabelの設定
- うーん、テストでES Modules構文とstage-x機能を利用するには
babel-jest
のインストールが必要と - Nodeの最新バージョンでもESMは対応しているけど、CJSとの併用で難点ありそうだから、これはいれないとESM使えないのかな
- jestテストはNodeで直接実行されるので、テスト用に babel-jestを有効にする必要がある、と記載があるな
いったん、 入れないでおこうかな動くかもしれないし。
簡単に、jsのテストを書いてみたら、import文動いたな。 人知れずbabelが動いているわけでもなさそうだし。なくてもいいんかな。
いや、動かなくなったなぜだ。。。とりあえず設定したらええんかな。
$ npm i -D babel-jest
package.jsonのjest.transformに以下追記
"^.+\\.js$": "<rootDir>/node_modules/babel-jest"
単純なテストは成功する用になったが、vueコンポーネントのテストではjestのオプションが不正っぽいエラーがでる。
ググって、babel-coreのバージョンを上げる方法がある(issue)、ということで、以下でバージョン上げた。
$ npm i -D babel-core@^7.0.0-bridge.0
テストファイルの配置
- デフォルトは
.spec.js
または.test.js
- package.jsonで定義可能
- 推奨
- テスト対象のコードのすぐとなりに
__test__
ディレクトリを作成
- テスト対象のコードのすぐとなりに
- jsetがスナップショットテストをするとき
- テストファイルのとなりに
__snapshots__
ディレクトリを作成する
- テストファイルのとなりに
これ、大事やん。__test__
をテスト対象コードの同階層に配置するのが推奨なのか。
カバレッジ
- jestの設定に
collectCaverage
オプションでカバレッジ取れる
{ "jest": { // ... "collectCoverage": true, "collectCoverageFrom": ["**/*.{js,vue}", "!**/node_modules/**"] } }
- デフォルトのカバレッジレポーターのカバレッジレポートは有効になる
Default: ["clover", "json", "lcov", "text"]
coverageReporters
でカスタマイズも可能["html", "text-summary"]
Specの例
テスト失敗した。
[vue-test-utils]: window is undefined, vue-test-utils needs to be run in a browser environment. You can run the tests in node using jsdom See https://vue-test-utils.vuejs.org/guides/#browser-environment for more details.
おー、jestのv28からjsdomが標準でインストールされなくなったのか。
$ npm i -D jest-environment-jsdom
package.jsonのjestブロックに以下を追記。
testEnvironment: "jest-environment-jsdom"
これで、テストは実行されるようになった。
だが、以下warningが出た。
console.error [vue-test-utils]: isVueInstance is deprecated and will be removed in the next major version. 5 | it("is a Vue instance", () => { 6 | const wrapper = mount(HelloWorld); > 7 | expect(wrapper.isVueInstance()).toBeTruthy(); | ^ 8 | }); 9 | }); 10 |
このwarning(error?)は、isVueInstance()がdeprecatedということを伝えるメッセージだった。将来的なバージョンでは削除されるらしい。
スナップショットの更新
jest --updateSnapshot
忘れそうなので、メモ。
スナップショットテスト
- vue コンポーネントをmount関数でマウントする
- wrapper.elementがHTMLのDOM要素っぽいので、それを
toMatchSnapshot
でアサートする。- snapshotディレクトリにスナップショットが生成される
test('renders correctly', () => { const wrapper = mount(Component) expect(wrapper.element).toMatchSnapshot() })
試しに、snapshotディレクトリに作成された.snapファイルを少し修正して、再度テストを実行したら、テストがfailした。
なるほどー
vue用?のカスタムシリアライザライブラリもある
$ npm install --save-dev jest-serializer-vue
package.jsonのjestブロックに設定
"jest" : { "snapshotSerializers" : [ "jest-serializer-vue"] }
うーん?スナップショットを再作成してみたけど、何も変わってない気がする。
多分、vueコンポーネントがシンプルだったからなのかな。結構DOM階層が多かったりすると、なにかきれいになったりするのかもしれない。
「インストール」の章はここまでかな。 mochaとかkarmaを使った場合の例もあったから、jestだけでいいや。
ガイド
はじめる
- 以下のデモリポジトリで手を動かしてみる
git clone https://github.com/vuejs/vue-test-utils-getting-started
- counter.js :vueコンポーネント
- test.js:テストファイル
マウンティングコンポーネント
import { mount } from '@vue/test-utils' import Conter from './counter' const wrapper = mount(Counter) // これでコンポーネントがマウントされる const vm = wrapper.vm // vueインスタンスにアクセス wrapper.html() // HTMLの文字列 wrapper.contains('button') // 要素が存在するかどうかの確認 //ユーザーインタラクションのシミュレーション const button = wrapper.find('button') // findはセレクターを受け取るのかな? button.trigger('click') // buttonのクリックイベントの発火
nextTickについて
- VueはDOM更新をまとめて処理し、非同期に適用する
- Vueが実際のDOM更新の実行を待つために普通は
Vue.nextTick
が必要 - 使い方を簡単にするために、
vue-test-utils
は更新を同期的にて適用するVue.nextTick
を使う必要はない- 非同期コールバックやプロミスの解決の操作のため、イベントループを明示的に進める必要があwる場合は、nextTickがまだ必要らしい
- テストファイルでnextTickを使う必要がある場合、nextTickの内部でPromiseを使っているのでnextTick内で発生したエラーはテストランナーによって補足されない
it ('だめパターン', done => { Vue.nextTick(() => { expect(true).toBe(false) // これわざとfailするアサートしているのかな done() }) }) it("OKパターン:グローバルエラーハンドラを指定", done => { Vue.config.errorHandler = done // これ。 Vue.nextTick(() => { expect(true).toBe(false) done() }) }) it("OKパターン:nextTickのpromseを返す", () => { return Vue.nextTIck().then(function() { expect(true).toBe(false) }) })
一般的なヒント
長いな。
何をテストするかを知る
- 完全なラインベースのカバレッジを目指すことはおすすめされない
- 内部をブラックボックスとして扱うことをおすすめ
- 入力:ユーザーのやり取りやプロパティの変更
- 出力:結果の描画やカスタムイベントの出力
- Counterコンポーネントの場合
- 入力:クリックをシミュレート
- 出力:描画された出力が1つ増加したのかを検証
Shallow 描画
- 単体テストはテスト対象のコンポーネントに焦点をあてる
- 子コンポーネントの動作の間接的な検証は避ける
vue-test-utils
のshallowMount
を使うと子コンポーネントをスタブによって描画せずにテスト対象コンポーネントをマウントできる- なんかうまく動かせなかったな。ただmountをshallowMountに変えただけだと
- 使い方が違うのかも
shallowMount
じゃなくてshallow
関数に変わってたな
- なんかうまく動かせなかったな。ただmountをshallowMountに変えただけだと
イベントの発行を検証する
wrapper.vm.$emit('foo') wrapper.vm.$emit('foo', 123) // wrapper.emitted() で実行されたイベント情報が得られる { foo: [ [], [ 123 ] ] }
このemitted()
で返されるイベント情報を使って、アサートする。
コンポーネントの状態を操作する
- setData()関数を使って、vueインスタンスのdataをセットする
- なんかうまくいかない、warrningがでる。
[Vue warn]: Avoid adding reactive properties to a Vue instance or its root $data at runtime - declare it upfront in the data option.
- vueのバージョンでもう許可されなくなったとかそんなもんかな。
- setProps()関数をつかって、プロパティを渡せる
- これはできた。
プロパティをモックする
- propertyを後からセットするんじゃなくて、mount時にわたすこともできる
mount(Component, { propsData: { hoge : "hoge" } })
非同期動作のテスト
- vue-test-utils は同期的にイベントをトリガします。従って、 Vue.nextTick() を実行する必要はない
- コールバックやPromiseのようなコンポーネントの非同期動作をテストする場合のテクニックの紹介
- axiosとか非同期APIを使う場合、普通にアサートしただけだと、アサート後に処理が実行される
- 非同期処理を待つ必要がある
import flushPromises from 'flush-promises'
を使って、可読性を上げることもできる。await flushPromises
することで、promiseを全部流せる
it("hoge test", done => { // 省略 wrapper.vm.$nextTick(() => { expect(wrapper.vm.hoge).toBe("hoge") done() }) })
TypeScriptと一緒に使う、という章はチラ見した。
- vueファイルを使うために、
vue-jest
を入れる - TypeScriptを使うために、
ts-jest
を入れる
くらいかな。
以上、で終わり!