はじめに
Nuxt.jsとTypeScriptで構築したプロジェクトに、Jestでテストを書く事になりました。
その際に引っかかった部分についてまとめています。
まだしばらくJestでテストを書く予定なので、順次追記していきます。
初期設定
i18nの文字列が取得できない
this.$t
はfunctionではないというエラーが表示され失敗するパターン
console.error
[Vue warn]: Error in render: "TypeError: this.$t is not a function"
この場合は$tにMockを設定することで回避が可能です。
また、i18nはプロジェクト全体で使用していることが多いと思うので、グローバルなMockに設定すると良いです。
jest起動時に実行する処理の設定
ファイル名は何でも良いのですが、ここではjest.setup.ts
というファイル名でjest.config.ts
と同階層に作成します。
jest.setup.ts
をjest.config.ts
のsetupFiles
というプロパティに設定することで、jest起動時に呼び出すように指定します。
import type { Config } from '@jest/types'
const config: Config.InitialOptions = {
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/$1',
'^~/(.*)$': '<rootDir>/$1',
'^vue$': 'vue/dist/vue.common.js',
},
moduleFileExtensions: ['ts', 'js', 'vue', 'json'],
transform: {
'^.+\\.ts$': 'ts-jest',
'^.+\\.js$': 'babel-jest',
'.*\\.(vue)$': 'vue-jest',
},
setupFiles: ['./jest.setup.ts'], // 追加
}
export default config
jest.setup.ts
では、i18nのMockを作成してconfig.mocks
に設定します。
これで全てのjestファイルのmockにi18nが適用されるようになります。
i18nMock
の処理は、単純に渡されたkeyに対してjsonから文字列を返しているだけのメソッドです。
import { config } from '@vue/test-utils'
import locales from '@/assets/json/locales/ja.json'
// i18nのモックを追加
const i18nMock = (key: string) => locales[key as keyof typeof locales]
const mocks = {
$t: i18nMock,
}
config.mocks = mocks
nuxt-linkが解決できない
nuxt-linkも同様にstubに置き換える必要があります。
こちらも先程のjest.setup.tsに設定を追加すると良いです。
nuxt-linkには、@vue/test-utilsからRouterLinkStubというものが提供されているため、それをimportしてきて使用します。
import { config, RouterLinkStub } from '@vue/test-utils'
:
省略
:
// nuxt-linkのstubを設定
config.stubs['nuxt-link'] = RouterLinkStub
基本的な使用方法
dataが呼び出せない
dataを触る場合は、wrapper.vm.$data
を参照します。
expect(wrapper.vm.$data.hoge).toBe('abc')
methodやcomputedが呼び出せない
methodやcomputedはdataと異なりvmから提供されていないため、vmをanyにキャストするか、
変数定義の際に型情報を追加してあげる必要があります。
キャストする方法は単純にメソッドを呼び出す時にanyにキャストします。
expect((wrapper.vm as any).increment(3)).toBe(3)
型情報を追加する方法は、変数宣言時に & { [key: string]: any }
とすることで、
vmにVueクラスのメソッド以外にも存在することを定義します。
こちらの方が、Vueクラスのプロパティが消えないことや、1箇所の定義ですむことからより良いかと思います。
let wrapper: Wrapper<Component & { [key: string]: any }>
wrapper = shallowMount(Component, { propsData: { ...defaultProps } })
expect(wrapper.vm.increment(3)).toBe(3)
個人的には、factoryメソッドを作成して、その中でwrapperを作成するのがおすすめです。
// wrapperを生成するfactory
const factory = (propsData?: PropsDataInterface) =>
shallowMount<Component & { [key: string]: any }>(Component, {
propsData: { ...defaultProps, ...propsData },
})
// wrapperにデフォルト値から変更するプロパティのみ渡す
const wrapper = factory({ ...defaultProps, items })
slotに値を入れる
slotに値を入れるには、shallowMountの第2引数にslotsというプロパティを渡すと設定ができます。
slot名は指定しない場合はdefault
となるため、slots: { default: 'slot contents' },
という値を設定すると反映されます。
describe('BasicButton', () => {
const factory = (propsData?: PropsDataInterface) =>
shallowMount<Component & { [key: string]: any }>(Component, {
propsData: { ...defaultProps, ...propsData },
slots: { default: 'slot contents' },
})
test('slotにコンテンツが挿入できること', () => {
const wrapper = factory()
expect(wrapper.html()).toBe('<button>slot contents</button>')
})
})
Emitを検知する
Emitがされたかを検知するには、wrapper.emitted('emit名')
を使用すると検知が出来ます。
emittedの戻りは2次元配列で表現されており、emit発火 * emitの内容
となります。
await wrapper.vm.click()
await wrapper.vm.click()
wrapper.emitted('success')
/*
[
[
"1回目のEmitの引数",
"1回目のEmitの戻り値",
],
[
"2回目のEmitの引数",
"2回目のEmitの戻り値",
],
]
*/
expect(wrapper.emitted('success')).toStrictEqual([[true, true], [true, true]])
// emitされなかった場合はundefinedが設定される
expect(wrapper.emitted('failed')).toBeUndefined()
応用/エラー対応
[Vue warn]: Unknown custom element
テスト対象の.vueファイルにコンポーネントが存在する場合に発生しました。
対応としては、shallowMountの際にstubs
を設定することで回避ができます。
console.error
[Vue warn]: Unknown custom element: <FooComponent> - did you register the component correctly? For recursive components, make sure to provide the "name" option.
const factory = (propsData?: PropsDataInterface) =>
shallowMount<Component & { [key: string]: any }>(Component, {
propsData: { ...defaultProps, ...propsData },
// stubを追加、コンポーネント名の文字列を渡す
stubs: ['FooComponent'],
})
this.$router.pushの確認
$router.pushにMockを設定するが、Mockにjest.fn()
を使用します。
jest.fn()
を使用することで、実行された回数や引数などが取得できるようになります。
// $router.pushのモックを定義
const pushMock = jest.fn()
const $router = { push: pushMock }
const factory = (propsData?: PropsDataInterface) =>
shallowMount<Component & { [key: string]: any }>(Component, {
propsData: { ...defaultProps, ...propsData },
// mockを追加
mocks: { $router },
})
test('クリック時の動作', async () => {
// toというPropにpathを渡すとそのpathに$router.pushするコンポーネント
const wrapper = factory({ to: '/hoge' })
// buttonタグを探してクリック
const button = wrapper.find('button')
await button.trigger('click')
// $router.pushが1回呼ばれたことの確認
expect(pushMock.mock.calls.length).toBe(1)
// 引数が '/hoge' であることの確認
expect(pushMock).toHaveBeenCalledWith('/hoge')
})
参考記事