自己紹介
この記事はSchoo Advent Calendar2025の8日目の記事です!
バックエンドエンジニア(PHP,Go)をメインに仕事しています。
ちょこちょこフロントエンドも書いています。
何の記事か。
Nuxt 3 + Vitest (@nuxt/test-utils) でユニットテストを実装の際、「テストはPassするのに、特定ファイルのCode Coverageがどうしても0%(または計測不能)になる」 という現象に遭遇し、AIに聞きながらいろいろ試してもなかなか解消せず、原因究明に時間を溶かしてしまう人が減ることを祈って書いています。
環境
- Nuxt 3
- Vitest:
^3.0.7 @nuxt/test-utils:^3.17.0"
構成
下記を参考に、ページ特有のコンポーネントやコンポーザブル関数を管理ができるようにあらかじめ設定をしています。
参照
app/
├── components/
├── composable/
└── pages/
└── index/
├── _components/ # ページ特有のcomponent
├── _composable/ # ページ特有のcomposable
├── tests
└── index.test.ts
└── index.vue
└── hoge
├── _components/
...
起きたこと
以下のような非常にシンプルな一覧ページコンポーネントを作成しました。
<script setup lang="ts">
import FeatureList from "@/pages/feature-name/index/_components/FeatureList.vue";
</script>
<template>
<div class="max-w-[1000px] mx-auto flex flex-col">
<h1>機能</h1>
<FeatureList />
</div>
</template>
これに対して、単純なスナップショットテストを作成しました。
import { mountSuspended } from "@nuxt/test-utils/runtime";
import Page from "@/pages/feature-name/index/index.vue";
import { describe, expect, it } from "vitest";
describe("pages/feature-name/index/index.vue", () => {
it("正しくレンダリングされること", async () => {
const wrapper = await mountSuspended(Page);
expect(wrapper.exists()).toBe(true); // PASS
expect(wrapper.html()).toMatchSnapshot(); // PASS
});
});
pnpm vitest run --coverage を実行すると、テスト自体はPassします。
しかし、カバレッジレポートを見ると…
----------------------------------------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------------------------------------------|---------|----------|---------|---------|-------------------
// 省略
app/pages/feature-name/index | 0 | 0 | 0 | 0 |
index.vue | 0 | 0 | 0 | 0 |
// 省略
実行されているはずなのに、カバレッジ上では「無(計測対象行なし)」として扱われていました。
なぜ・・・?
試したこと(けど効果がなかったこと)
問題を切り分けるために、以下の手順でかなり泥臭く検証を行いました。
1. 基本的な実行
コマンド: pnpm vitest run app/pages/feature-name/index/index.test.ts --coverage
- 結果: テスト自体は Pass する。
- カバレッジ: 計測されず (0% / Uncovered)。
2. テストファイルの配置を変更
ディレクトリ構成による自動検出の挙動を疑い、テストファイルを移動してみました。
-
pages/index/tests/index.test.tsに移動して実行 → 計測されず -
pages/index/index.test.ts(ソースの真横) に移動して実行 → 計測されず
3. importパスの変更
エイリアス (@/) がSourceMap解決の邪魔をしている可能性を考え、相対パスに変更しました。
-
import Page from "@/pages/..."をimport Page from "./index.vue"等に変更して実行 → 計測されず
4. Configでの強制指定
vitest.config.ts がファイルを無視している可能性を考え、明示的に include を指定してみました。
-
include: ["app/pages/feature-name/index/**"]を追加して実行 → 計測されず
5. ディレクトリを変えてみる
「pages/ ディレクトリ特有の問題ではないか?」という仮説を立て、ファイルをコピーして検証してみました。
-
feature-name/index/_components以下に、対象のindex.vueをコピーして配置 - そちらに対して同様のテストを実行。
- 結果: 移動したファイル(_components配下)だけ正常に計測された (100%)
原因の特定
Nuxt Test Utilsにこのようなissueがあり、こちらの事象みたいです。(2025/12時点でも未解決)
https://github.com/nuxt/test-utils/issues/891
when I add .vue files to the pages/ directory, the coverage is not measured.
このようにあったので同じ事象と踏んでいます。
想定される原因
- Nuxt・Vitest の相互作用によるバグの可能性が高い
- Vueでは発生せず、Nuxt の
pagesディレクトリ特有の処理が関係していそう - Vitest v3 + coverage-v8 周りの問題も疑わしい
-
Coverage Provider の挙動差が影響している可能性(特にV8側のソースマップ依存)
- 公式ドキュメント:https://vitest.dev/guide/coverage
- V8 はV8のプロファイラ(Node の node:inspector や Chrome DevTools Protocol)に指示して、どの行が実行されたかを記録する
- Istanbul はソースコードの各、各関数、各分岐に「カウンター(計測用コード)」を埋め込むという方法でカバレッジを図る
- 公式ドキュメント:https://vitest.dev/guide/coverage
解決策
package.json にて
カバレッジのライブラリの@vitest/coverage-v8 → @vitest/coverage-istanbulに変更したらカバレッジが正確に取れるようになりました!🎉
# package.json
- "@vitest/coverage-v8": "3.0.7",
+ "@vitest/coverage-istanbul": "3.0.7"
まとめ
- Nuxt 3の
pages配下のファイルは、V8だとカバレッジが正しく取れないことがある - Coverage Provider を Istanbul に変更することで解決できる
- ハマった際は公式のissuesに同じ問題があるかもしれないので、AIに頼りすぎずに、各種ライブラリのissueを見てみよう
同じ現象で「テストは通ってるのになんで!?」となっている方の参考になれば幸いです!
後書き
調査、原因解明に協力していただいた @schoo_tetoneさん、@so_fujishima-schooさんありがとうございました!
Schooでは一緒に働く仲間を募集しています!