Cypressはe2eからUIコンポーネントまでの広い範囲を扱えるテスティングフレームワーク。
この記事では、PiniaのストアのユニットテストをCypressを使って行う方法のアイデアを提示する。
はじめに
vue.jsのテンプレートを使用し、Cypressを組み込んでプロジェクトを作成した場合、以下の構成でテストが用意される。
-
./cypress/フォルダ下にe2eのテスト -
./src/components/__tests__/フォルダ下にコンポーネントテスト
ここに、`./stores/下に配置したユニットテストを実行させたいが、
CypressのTesting Type-Specific Optionsはe2eとcomponentの設定しか用意されていなく、そのままではストアの設定を加えることはできない。
方法
まず、ストアのフォルダ構成は、以下のようになると想定している。
📁 /
├── src
├── components
├── stores
├── 各ストアのフォルダ
├── index.ts ここにストアの実装
└── test.cy.ts ここにユニットテストの実装
ストアの近くにユニットテストを置きたかったのと、フォルダ分けせずstores直下にストアの実装とユニットテストの実装を置くと、フォルダが肥大化していや〜んな感じになるのが好みじゃないため。
こうしなければならないというわけではないです。
設定内容
Cypressのコマンドラインオプションとして、明示的に設定ファイルを渡すことができる。
そこで、package.jsonのscript設定を以下のように記述。
- "test:unit": "cypress run --component",
- "test:unit:dev": "cypress open --component",
+ "test:c": "cypress run --component",
+ "test:c:dev": "cypress open --component",
+ "test:u": "cypress run --component --config-file cypress.stores.config.ts",
元々あった、コンポーネントテストを実行するためのコマンドtest:unitをtest:cに変更している。(cはcomponentのc)
ユニットテスト用の設定ファイルは、以下のように記述した。
import { defineConfig } from 'cypress'
export default defineConfig({
component: {
specPattern: 'src/stores/**/*.{cy,spec}.{js,ts,jsx,tsx}',
devServer: {
framework: 'vue',
bundler: 'vite'
}
}
})
ユニットテストなのでdevServerはなくてもいいのだけど、必須のためテンプレートでの記述をそのまま採用している。1
テストの記述
あとは、ストアのためのユニットテストを書く。
幸いにして、他のBDDフレームワークとだいたい同じ記法をサポートしてくれている。
たとえば、vueテンプレートでサンプルとして用意されるcounterストアの場合は以下のようになる。
import { setActivePinia, createPinia } from 'pinia'
import { useCounterStore } from '.'
describe('Counter store', () => {
beforeEach(() => {
setActivePinia(createPinia())
})
it ('increment', () => {
const store = useCounterStore()
expect(store.count).to.equal(0)
store.count++
expect(store.count).to.equal(1)
store.count++
expect(store.count).to.equal(2)
})
it ('double increment', () => {
const store = useCounterStore()
expect(store.doubleCount).to.equal(0)
store.count++
expect(store.doubleCount).to.equal(2)
store.count++
expect(store.doubleCount).to.equal(4)
})
})
Piniaの公式サイトで紹介された内容と比べると、以下の違い程度で済むのも魅力。
- expect(counter.count).toBe(0)
+ expect(store.count).to.equal(0)
おまけ
Cypressの一番気に入っている点が、descrive > context > itと階層化して書けること。
最近ははやりのjestとかjestとかjestはcontextが書けない(書けてもなんか不恰好)ことに不満だったのでよき。2
おまけその2
ユニットテスで、
import { useCounterStore } from '.'
と記述していて、おやっ?と思った人のための補足。
vite.config.tsのエイリアスの解決ルールとして以下のように記述しているため。
export default defineConfig({
plugins: [
vue(),
],
resolve: {
alias: {
+ '@stores/*': fileURLToPath(new URL('./src/stores/**/index.ts', import.meta.url)),
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})
コンポーネト側でのインポートで、以下のように手抜きをしたかったから。
import { useCounterStore } from '@/stores/counter'