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'