Cypressのコンポーネントテストは、ReactやVueのコンポーネントを1つだけ実ブラウザにマウントし、表示と操作を確かめる機能です。テストの中心になるのが cy.mount で、コンポーネントを描画したあとは cy.get と should でE2Eテストと同じ書き方ができます。
注意したいのは対応環境の変化の速さです。2026年9月1日公開のCypress 16.0.0で、コンポーネントテストはVite 8以上・Angular 21以上・Next.js 15.0.4以上が必須になりました。Vite 7のプロジェクトはCypressを16に上げると起動時にエラーで止まり、Angular 20は公式サポートの対象から外れます。
この記事では最新版16.2.1(2026年10月28日公開)を前提に、cy.mountの書き方、devServerの設定、旧版から移行するときの回避策を整理します。Cypressそのものの仕組みやE2Eテストの書き方はCypress E2Eテストの使い方|インストールからcy.intercept・CI実行まで【Cypress 16対応】で扱っています。
まとめ
- コンポーネントテストは単一のコンポーネントを実ブラウザにマウントして検証する。
waitForやact()は不要 cy.mountは組み込みコマンドではない。初期設定時にcypress/support/component.{js,ts}へ登録される- Cypress 16の公式対応はReact 18-19・Vue 3・Angular 21-22・Next.js 15.0.4以上・16・Svelte 5(Alpha)。バンドラはVite 8かWebpack 5
devServer.frameworkに書ける公式値は react / vue / svelte / next / angular の5つ- Viteを使う構成では、設定ファイルをESM(
.mjs・"type": "module"・.ts)にする - 描画時の例外は同期的に投げられない。
cy.on('uncaught:exception')で受けて検証する - 要素は
data-cyなどテスト専用の属性で指定し、CSSクラスやDOM構造に依存させない - Vite 7やAngular 20を当面使うなら、非推奨の
@cypress/vite-dev-server@7や@cypress/angular@4で一時的にしのげる
コンポーネントテストの仕組みとE2Eテストとの分担
実ブラウザにマウントする方式とjsdomとの違い
Jest+React Testing Libraryのような構成は、Node.js上のjsdom(模擬DOM)でコンポーネントを描画します。Cypressのコンポーネントテストは、Vite や Webpack の開発サーバーでコンポーネントをバンドルし、ChromeやFirefoxの実ブラウザに描画します。CSSが効いた状態で動くため、要素が重なってクリックできない、スクロールしないと見えないといった不具合も拾えます。
描画の完了を待つコードも要りません。公式のComponent Testing入門は、アサーションがコンポーネントの描画・更新後に実行されるため、waitFor や act()、手書きのタイムアウトは不要だと説明しています。cy.get と should は既定で4秒まで自動的に再試行します。
E2Eテストに回すべき検証の線引き
入力ごとの表示差分、エラー表示、ボタンの活性制御のようにコンポーネント単体で完結する分岐はコンポーネントテストに置きます。ログインや画面遷移、API通信をまたぐ業務フローはE2Eテストの担当です。
Next.jsのページは特に注意が要ります。React向けの公式ページは、getServerSideProps や getStaticProps がサーバー側でしか動かないため、ページをコンポーネントテストで描画するとpropsが undefined になると注意しています。ページはE2Eテスト、ページを構成する部品はコンポーネントテスト、と分けてください。
Cypress 16で使えるフレームワークとバンドラ
Cypress 16.2.1の公式対応は次のとおりです。公式の対応表と、Cypress本体の packages/scaffold-config/src/dependencies.ts(v16.2.1タグ)に書かれた最小バージョンを突き合わせて作りました。
| framework の値 | UIライブラリ | バンドラ | 状態 |
|---|---|---|---|
| react | React 18・19 | Vite 8 / Webpack 5 | 正式 |
| next | Next.js 15.0.4以上・16 | Webpack 5のみ | 正式 |
| vue | Vue 3 | Vite 8 / Webpack 5 | 正式 |
| angular | Angular 21・22 | Webpack 5のみ | 正式 |
| svelte | Svelte 5 | Vite 8 / Webpack 5 | Alpha |
Nuxt・Remix・SvelteKitのような値はありません。Nuxtは vue+vite として扱います。公式以外ではQwikとLitのコミュニティ製定義があり、cypress-ct-* という名前のパッケージを framework に指定して使います。
Next.jsはバンドラがWebpack 5に固定されている点も見落としやすいところです。Next.js 16の開発サーバーはTurbopackが既定ですが、Cypress 15.7.0のchangelogはTurbopackについて「not yet supported within Cypress」と明記し、Next.jsのコンポーネントはWebpackでバンドルしています。Turbopack固有の設定はテスト側に反映されません。
React+Viteでコンポーネントテストを導入する手順
インストールと設定ファイルの生成
Cypress 16はNode.js 22・24・26以上が必要です(npmの engines は ^22.0.0 || ^24.0.0 || >=26.0.0)。Node.js 20はサポート対象外です。npmは engine-strict を設定していなければ EBADENGINE の警告を出すだけでインストールが進むので、CIのログで警告を見落とさないでください。
npm install --save-dev cypress
npx cypress open --component
Cypressの画面でComponent Testingを選ぶと、フレームワークとバンドラが自動検出され、必要な依存の確認画面に進みます。続けて cypress.config と cypress/support/ 配下のファイルが生成されます。Cypress 14で削除された cypress open-ct は、いまは存在しないコマンドです。
cypress.configのdevServer設定とESM形式の要件
生成される設定の中心は component.devServer です。プロジェクトの vite.config は自動で読み込まれるため、通常はフレームワークとバンドラの2つを書けば足ります。
// cypress.config.ts
import { defineConfig } from 'cypress'
export default defineConfig({
component: {
devServer: {
framework: 'react',
bundler: 'vite',
},
},
})
Viteを使う場合は設定ファイルの形式に制約があります。Cypress 15.0.0で @cypress/vite-dev-server がESM専用になり、公式の移行ガイドは、cypress.config.cjs や、"type": "module" の無い package.json で require/module.exports を使う cypress.config.js をESMへ移すよう求めています。.mjs にする、package.jsonに "type": "module" を足す、.ts にする、のいずれかです。現行の8.0.0もpackage.jsonが "type": "module" で、peerDependenciesは vite: ^8.0.0・cypress: >=16.0.0 です。
Nuxtのようにバンドラ設定をフレームワークが内部で組み立てる構成では、Cypressが nuxt.config を実行しません。@/ や ~/ のエイリアスは viteConfig に自分で書きます。
// cypress.config.ts(Nuxt 3以降)
import { fileURLToPath } from 'node:url'
import { defineConfig } from 'cypress'
export default defineConfig({
component: {
devServer: {
framework: 'vue',
bundler: 'vite',
viteConfig: {
resolve: {
alias: {
'@': fileURLToPath(new URL('./', import.meta.url)),
'~': fileURLToPath(new URL('./', import.meta.url)),
},
},
},
},
},
})
Nuxtが自動インポートする ref や useRoute も、単体でマウントしたコンポーネントには届きません。Vue向けの公式ページは、コンポーネント側で明示的にimportするか、同等のViteプラグインを viteConfig に足すよう案内しています。tsconfig.json の compilerOptions.paths だけで定義したエイリアスも読まれません。公式のDev Server設定のページは、vite-tsconfig-paths を vite.config(または viteConfig)に入れる方法を示しています。
cy.mountの書き方とフレームワーク別の引数
Reactでのprops指定と表示の検証
Reactではテスト内にJSXをそのまま書きます。propsは通常のJSX属性です。
import Stepper from './Stepper'
describe('<Stepper />', () => {
it('count propで初期値を指定できる', () => {
cy.mount(<Stepper count={100} />)
cy.get('[data-cy=counter]').should('have.text', '100')
})
})
cy.mount が返す値の rerender を使うと、マウントし直さずにpropsだけ変えた再描画を試せます。rerender は同じkeyで再描画するため、Stepperのようにpropsを useState の初期値にだけ使うコンポーネントでは表示が変わりません。propsをそのまま描画するコンポーネントで試します。
const Counter = ({ count }) => <span data-cy="counter">{count}</span>
it('countの変更に追従する', () => {
cy.mount(<Counter count={1} />).then(({ rerender }) => {
rerender(<Counter count={5} />)
})
cy.get('[data-cy=counter]').should('have.text', '5')
})
Vue・Svelte・Angularでの引数の違い
Vue・Svelteはimportしたコンポーネントをそのまま、Angularはコンポーネントクラスを渡します。propsの渡し方だけが異なります。
// いずれも it() の中に置く
// Vue / Svelte
cy.mount(Stepper, { props: { count: 100 } })
// Angular
cy.mount(StepperComponent, { componentProperties: { count: 100 } })
Angularは16.0.0でゾーンレスのテストハーネスが既定になり、zone.js のインストールは不要になりました。以前 cypress/angular-zoneless からimportしていた場合は、cypress/angular に書き換えます。
cy.spyによるイベント発火の検証
イベントが正しい値で発火したかは、cy.spy() で作ったスパイをpropsとして渡し、エイリアス経由で確かめます。
it('+を押すと1増えた値でonChangeが呼ばれる', () => {
const onChangeSpy = cy.spy().as('onChangeSpy')
cy.mount(<Stepper onChange={onChangeSpy} />)
cy.get('[data-cy=increment]').click()
cy.get('@onChangeSpy').should('have.been.calledWith', 1)
})
Providerで包むカスタムmountコマンド
Cypressには組み込みの cy.mount() がありません。cy.mountの公式リファレンスは、サポートファイルにカスタムコマンドとして定義し、Providerで包むなどの共通処理をそこに寄せる方法を推奨しています。React Routerのフックを使うコンポーネントなら、次のように初期URLを差し替えられる形にしておくと、テストごとにRouterを書かずに済みます。
// cypress/support/component.tsx
import { mount } from 'cypress/react'
import { MemoryRouter, type MemoryRouterProps } from 'react-router-dom'
declare global {
namespace Cypress {
interface Chainable {
mount(
component: React.ReactNode,
options?: Parameters<typeof mount>[1] & { routerProps?: MemoryRouterProps }
): ReturnType<typeof mount>
}
}
}
Cypress.Commands.add('mount', (component, options = {}) => {
const { routerProps = { initialEntries: ['/'] }, ...mountOptions } = options
return mount(<MemoryRouter {...routerProps}>{component}</MemoryRouter>, mountOptions)
})
テスト側は cy.mount(<Nav />, { routerProps: { initialEntries: ['/login'] } }) のように呼びます。ReduxやReact Queryのストアを包む場合も、同じ場所に足していきます。
描画エラーとError Boundaryを検証する書き方
必須のpropsが無いときに例外を投げる仕様は、expect(() => cy.mount(<UserProfile />)).to.throw() では検証できません。公式のReact作例によると、cy.mount() はマウントをキューに積んで描画前に戻る非同期コマンドで、描画中の例外は同期的には投げられず、未捕捉の例外として届くためです。
Cypressは未捕捉の例外でテストを失敗させるので、uncaught:exception イベントで受け、期待どおりのエラーか確かめてから false を返します。
it('userが無いと描画エラーになる', () => {
cy.on('uncaught:exception', (err) => {
expect(err.message).to.include('user is required')
return false
})
cy.mount(<UserProfile />)
})
Error Boundaryで包んだ場合も同じです。代替UIは描画されますが、例外そのものはCypressまで届きます。代替UIの表示を確かめるテストでも uncaught:exception の抑止は必要で、想定したメッセージの例外だけを握りつぶすように条件を付けてください。何でも return false にすると、無関係な不具合まで見逃します。
data-cy属性で要素を指定するセレクタ設計
Cypressの公式ベストプラクティスは、data-cy のようなテスト専用の属性で要素を指定する方法を推奨しています。CSSクラスはデザイン変更で、タグ名や階層はマークアップの整理で変わりますが、テスト専用の属性はテストのためだけに存在するので壊れにくいからです。
// 壊れやすい
cy.get('.btn.btn-primary').click()
cy.get('div > button:nth-child(2)').click()
// 推奨
cy.get('[data-cy=increment]').click()
属性名は data-cy・data-test・data-testid のどれでも動作は同じです。迷ったら、すでにテスト資産がある側に寄せてください。Testing Libraryの getByTestId を併用しているなら data-testid、Cypressだけなら公式作例と同じ data-cy を選びます。プロジェクト内で混在させるとレビューで指定漏れに気づけなくなるため、1つに決めてlintで縛るのが確実です。ロケータをテストコードから切り離す設計はPage Object Model(POM)とは?Playwright・Seleniumの実装例と保守コストの実測でも測っています。
実行コマンドとCIで回すときの設定
手元では cypress open --component で画面を見ながら書き、CIでは cypress run --component でヘッドレス実行します。
npx cypress run --component --browser chrome
npx cypress run --component --spec "src/components/Stepper.cy.tsx"
--browser は省略しないほうが安全です。Cypress 16.0.0で同梱のElectronブラウザが非推奨になり、将来のメジャー版で削除されると予告されています。既定のElectronに頼ったCIは、そのとき一斉に動かなくなります。cypress.config の defaultBrowser で指定しても構いません。
GitHub Actionsでは公式の cypress-io/github-action(2026年11月1日時点の最新はv7.4.9)に component: true を渡すと、コンポーネントテストとして実行されます。
- uses: cypress-io/github-action@v7
with:
component: true
browser: chrome
Webpack構成では justInTimeCompile が14.0.0から既定で有効で、実行するspecに関係するリソースだけを直前にコンパイルします。Viteには適用されない設定なので、Vite構成で無効化しようとしても効果はありません。テスト自動化をCI/CDのパイプラインに組み込む体制づくりから相談したい場合は、DevOps・CI/CD導入支援で承っています。
Cypress 14〜16で削除された対応環境と移行手順
Cypress 13以前の手順のまま残っている解説も少なくありません。手順どおりに進めて動かないときは、まず次の削除に当たっていないか確認してください。
| 版(公開日) | コンポーネントテストで削除されたもの |
|---|---|
| 14.0.0(2025-01-16) | create-react-app・Vue CLI・Vue 2・React 16/17 |
| 14.0.0(2025-01-16) | Next.js 10〜13・Nuxt 2・Angular 13〜16(最小17.2.0)・Svelte 3/4 |
| 14.0.0(2025-01-16) | cypress open-ct / run-ct・cypress/react18 |
| 15.0.0(2025-08-20) | Angular 17・Vite 4・CommonJSからのvite-dev-server利用 |
| 16.0.0(2026-09-01) | Angular 18〜20・Vite 5〜7・Next.js 14・Node.js 20/25(全体) |
Cypress 16.0.0で必要になる対応
Vite 7のままCypress 16に上げると、起動時に次のエラーで止まります。@cypress/vite-dev-server 8.0.0の getVite.js が、プロジェクトのViteのメジャー版が8未満なら例外を投げる実装です。
ViteVersionNotSupportedError: Vite 8 is the required version to use cypress/vite-dev-server. Found Vite version v7.x.x
正攻法はViteを8に上げることです。古いメジャー版に固定された @vitejs/* プラグインがあれば、先にそちらを上げます。Angularは21以上が条件で、@angular/platform-browser-dynamic は @angular/platform-browser に置き換わりました(Angular 21は既定で後者を使うため追加作業は不要で、前者は依存から外せます)。マウント時のオプション autoSpyOutputs と autoDetectChanges は削除されました。Next.jsは15.0.4以上か16が必要です。
コンポーネントテスト以外では、Cypress.env() の削除がサポートファイルにも響きます。カスタムmountコマンドの中で Cypress.env() から設定値を読んでいた場合、16では例外になります。ブラウザから同期的に読む公開値は Cypress.expose()、秘密情報は cy.env() へ移します。
旧版を当面使い続ける回避策
すぐにViteやAngularを上げられない場合、公式の移行ガイドは非推奨パッケージを個別に入れる一時的な回避策を示しています。どちらもサポート対象外なので、移行までのつなぎと割り切ってください。
# Vite 5〜7を使い続ける
npm install --save-dev @cypress/vite-dev-server@7
# Angular 21未満を使い続ける(import元を '@cypress/angular' に変更)
npm install --save-dev @cypress/angular@4
@cypress/vite-dev-server@7 を使うときは、devServer をオブジェクトでなく関数で書き、パッケージから読み込んだ devServer に framework と bundler: 'vite' を渡します。
// cypress.config.ts
import { defineConfig } from 'cypress'
import { devServer } from '@cypress/vite-dev-server'
export default defineConfig({
component: {
devServer(devServerConfig) {
return devServer({ ...devServerConfig, framework: 'react', bundler: 'vite' })
},
},
})
Cypressのコンポーネントテストを選ぶ条件と別ツールが向く場面
実ブラウザでコンポーネントを動かす方法はCypressだけではありません。Vitest Browser ModeとPlaywright Component Testingが同じ領域にあり、選び方の軸は「すでに何を使っているか」です。
| 手段 | 向く既存環境 | 弱い場面 |
|---|---|---|
| Cypress CT | E2EをCypressで書いている | Vite 7以前・Angular 20以前 |
| Vitest Browser Mode | 単体テストがVitest | E2Eは別ツールが必要 |
| Playwright CT | E2EをPlaywrightで書いている | 1.62でstory方式へ移行し旧方式は書き直し |
| jsdom+Testing Library | CIの速度を最優先する | CSSやレイアウト起因の不具合 |
Cypressを選んでよいのは、E2EテストがすでにCypressで、テストランナーとコマンド体系を1つにそろえたい場合です。cy.get・cy.intercept・カスタムコマンドがそのまま使え、失敗時はコマンドログから描画の途中経過を追えます。
逆に、単体テストをVitestで書いていてE2Eはまだ無い、という状態でCypressを入れるのは勧めません。ランナーが2つになり、Vite 8やNode.js 22以上といったCypress側の条件にプロジェクトの更新を縛られます。この場合はVitest Browser Modeの使い方|Playwrightでの導入・headless設定とjsdomとの使い分け【Vitest 5対応】のほうが既存の設定を流用できます。E2EをPlaywrightで書いているならPlaywright Component Testingの使い方|1.62のstory方式と移行手順が候補です。Angular 20以前やVite 7以前から当面動けないプロジェクトも、Cypress 15系に留めるか別ツールを選ぶほうが現実的です。
よくある質問
cy.mountが「is not a function」で失敗するのはなぜですか?
cy.mount は組み込みコマンドではなく、cypress/support/component.{js,ts} で登録して初めて使えます。サポートファイルが読み込まれていないか、登録の行が消えている可能性があります。import { mount } from 'cypress/react' と Cypress.Commands.add('mount', mount) があるか確認してください(Reactの場合。Vueは cypress/vue、Angularは cypress/angular、Svelteは cypress/svelte からimportします)。
Cypress 16に上げたらViteのエラーで起動しません。どうすればよいですか?
Cypress 16のコンポーネントテストはVite 8以上が必須です。Viteを8へ上げるのが正攻法で、すぐに上げられない場合は @cypress/vite-dev-server@7 を個別に入れて devServer を関数で書く回避策があります。
Next.jsのページ全体をコンポーネントテストできますか?
描画はできますが、getServerSideProps や getStaticProps は実行されず、propsが undefined になります。公式もページはE2Eテスト、個々のコンポーネントはコンポーネントテストで検証するよう推奨しています。
create-react-appやVue 2のプロジェクトで使えますか?
Cypress 14.0.0で両方のサポートが削除されたため、現行版では使えません。ViteかWebpack 5の構成へ移行し、VueはVue 3へ上げる必要があります。
data-cyとdata-testidはどちらを使うべきですか?
動作に違いはなく、どちらもCSSの属性セレクタとして扱われます。Testing Libraryを併用しているなら data-testid、Cypressだけなら公式作例と同じ data-cy にそろえ、プロジェクト内では混在させないでください。
関連記事
- Cypress E2Eテストの使い方|インストールからcy.intercept・CI実行まで【Cypress 16対応】
- Playwright Component Testingの使い方|1.62のstory方式と移行手順
- Vitest Browser Modeの使い方|Playwrightでの導入・headless設定とjsdomとの使い分け【Vitest 5対応】
- Vitestの使い方と書き方:読み方・設定からReact Testing Libraryのコンポーネントテストまで【Vitest 5対応】
- Page Object Model(POM)とは?Playwright・Seleniumの実装例と保守コストの実測