axiosのモック化にかなり手間取ったため、作業メモとして残しておこうと思いました。
必要なライブラリ
- @vue/cli-plugin-unit-jest
- @vue/test-utils
- flush-promises
jestを使ってモック化するため、cli-plugin-unit-jest
を入れました。
最後のflush-promises
はVue Test Utilsの公式サイトで非同期動作のテクニックとして紹介されており、非同期処理を強制的に(?)実行させるためにインストールしました。
非同期のテスト
テストコード
axiosのインポート
import axios from 'axios'
まずはaxiosモジュールをインポートします。
システムによっては、axiosの設定をindex.js
に書いていると思いますが、テストコードではこのindex.js
ではなく、生(と言っていいかは謎ですが)のaxiosをimportします。
理由としては、index.js
をimportすると、そのファイルに書かれている、例えばaxios.create等のメソッドもモック化しないといけないからです。
もちろん、別でindex.js
のテストは必要なのですが、今回のテスト対象は、axios.postやgetを使っているコンポーネントであるため、index.js
ではなく、axios
をimportします。
axiosのモック化
jest.mock('axios')
axiosモジュールをモック化します。
これで、プロダクトコードでaxiosを呼び出した場合、これから登録するモック関数が呼ばれるようになります。
このjest.mock('axios')
は、describeの前に実行してください。
mountまたはshallowMountの実行時オプションにsync: falseを付ける
const wrapper = mount(sampleComponent, { sync: false })
axios.postにモック関数を登録する
例えばこのようなプロダクトコードがあるとします。
methods: {
aFunc() {
axios.post('/sample')
.then(response => {
console.log('成功')
})
.catch(error => {
console.log('失敗')
})
}
}
このaxios.postから返すレスポンスをモックにします。
const response = {
message: '成功'
}
axios.post.mockImplementationOnce((url) => {
return Promise.resolve(response)
})
mockImplementationOnce
は、post
が呼び出されたときに「一度だけ」関数の中が実行されます。
もし、テスト対象のプロダクトコードでpostが2回呼び出されていた時には、1回目のpostは上記で設定したPromise.resolve
が返りますが、2回目のpostはエラーになります。
この場合の解決策としては2つあります。それについては後述します。
ちなみにプロダクトコードでthis.$http
のように使われている場合は、テストコードのimport文直後くらいにprototype.$http = axios
を入れてください。
私はcreateLocalVueでlocalVueを作成し、それに対して定義してます。
const localVue = createLocalVue()
localVue.prototype.$http = axios
モック関数の他例
もし、テスト対象のプロダクトコードでpostが2回呼び出されていた時には、1回目のpostは上記で設定した
Promise.resolve
が返りますが、2回目のpostはエラーになります。
例えばこのようなコードがあった場合は、mockImplementationOnce
を一つ指定しただけだとエラーになります。
methods: {
aFunc() {
axios.post('/sample') // post呼び出し1回目
.then(response => {
console.log('/sample成功')
axios.post('/sample2') // post呼び出し2回目
.then(response => {
console.log('/sample2成功')
})
.catch(error => {
console.log('/sample2失敗')
})
})
.catch(error => {
console.log('/sample失敗')
})
}
}
ではどうすればよいか、ですが、求めるレスポンスによって2通りのやり方があります。
レスポンスが同じでも良い場合
jest.fn().mockImplementation
を使います。
const response = {
message: '成功'
}
axios.post.mockImplementation((url) => {
return Promise.resolve(response)
})
こうするとaxios.post
が呼ばれたときには 常にresponseが返されます。
レスポンスが異なる場合
jest.fn().mockImplementationOnce
を呼び出されるAPIの数分定義します。
const response1 = {
message: '成功'
}
const response2 = {
message: '失敗'
}
axios.post.mockImplementationOnce((url) => {
return Promise.resolve(response1)
}).mockImplementationOnce((url) => {
return Promise.resolve(response2)
})
こうすることで、1回目のAPI呼び出しのときはresponse1が返り、2回目のAPI呼び出しの時はresponse2が返ります。
モックの検証
きちんと想定通りのモックが呼ばれているかを検証します。
検証したいことによって、いろいろとメソッドが用意されていますが、ここではtoHaveBeenCalledWith
を使います。
その他のメソッドについてはJestのドキュメントをご覧ください。
レスポンスが異なる場合の検証をしてみましょう。
expect(axios.post).toHaveBeenCalledWith('/sample')
expect(axios.post).toHaveBeenCalledWith('/sample2')
expect
の引数には、実行したモックを指定します。
toHaveBeenCalledWith
の引数には、APIが実行されたときの引数を指定します。
今回、API実行時にはurlのみを引数としてaxios.postに渡しているため、それを指定します。
params等の引数を第2引数に渡している場合は、expect(axios.post).toHaveBeenCalledWith('/sample', { param1: 'aaa' })
のように渡します。
axiosではなく作ったモジュールの一部をモック化する!
- モジュールを読み込む
import sampleModule from '@/mixin/sampleModule.js'
-
jest.mock
でモック化する
jest.mock('@/mixin/sampleModule')
- モック化したいメソッドに
jest.fn
を入れ込む
sampleModule.methods.sampleMethod = jest.fn((arg1, arg2) => {
return data
})
これでモック化できます。
テスト対象コンポーネントのメソッドをモック化する!
-
jest.mock
でモック化する
const sampleMethod = jest.fn()
- mountオプションに
methods
を指定する
const wrapper = mount(sampleComponent, {
localVue,
methods: { sampleMethod }
})
ちゃんと調べたわけではないのですが、どうやらconstの名前のメソッドがモック化されるようです。
以上です!