すでにCypressをエンドツーエンドテストで使っているなら、UIを操作せずにAPIを直接叩き、ステータスコードやJSONレスポンスを検証できます。cy.request() はブラウザではなくNodeプロセスから実際のHTTPリクエストを送るため、エンドポイント単体の検証や、UIテスト前の状態セットアップに使えます。
この記事では、CypressでAPIをテストする実装手順を整理します。cy.request() の基本、レスポンスのアサート、認証トークンの再利用、cy.intercept() によるブラウザ通信のスタブ化、そしてCypressとAPIネイティブツールの使い分けを扱います。
CypressでAPIをテストする理由
Cypressの多くのテストはブラウザ操作を中心に作られます。ただし、UIで確認している挙動の多くはAPIに依存しています。
例:
-
/loginがトークンを返す -
/usersが期待したスキーマで返る -
POST /postsがレコードを作成する - エラー時にUIが適切な表示をする
APIを直接テストすると、次のメリットがあります。
- UIレンダリングを待たないため、テストが速い
- DOM要素の待機やクリック操作が不要
-
cy.request()でテストデータを素早く作成できる - UIテストの前提条件をコードで安定して準備できる
たとえば、サインアップフォームをクリックしてユーザーを作る代わりに、APIへ1回 POST してから本当に検証したい画面へ進めます。
すでにCypressを導入しているプロジェクトなら、新しいテストランナーを追加せずに、同じスペックファイル、同じChaiアサーション、同じCI実行の中でAPIチェックを追加できます。
cy.request() の基本
cy.request() はHTTPリクエストを送信し、レスポンスを返すCypressコマンドです。ブラウザではなくCypressのNodeプロセスから実行されるため、CORSや同一オリジンポリシーの制約を受けません。
主な呼び出し形式は次のとおりです。
cy.request(url)
cy.request(url, body)
cy.request(method, url)
cy.request(method, url, body)
cy.request(options)
最小構成ではURLだけを渡します。メソッドを省略すると GET になります。
cy.request('https://jsonplaceholder.typicode.com/users')
POST、ヘッダー、ボディ、クエリパラメータなどを指定したい場合は、オプションオブジェクトを使います。
cy.request({
method: 'POST',
url: 'https://jsonplaceholder.typicode.com/posts',
headers: {
'Content-Type': 'application/json'
},
body: {
title: 'API test',
body: 'created from Cypress',
userId: 1
}
})
よく使うオプションは次のとおりです。
| オプション | 用途 |
|---|---|
method |
GET、POST、PUT、PATCH、DELETE など。デフォルトは GET
|
url |
リクエスト先エンドポイント |
body |
リクエストボディ。オブジェクトはJSONとして送信される |
headers |
リクエストヘッダー |
auth |
HTTPベーシック認証 |
qs |
クエリ文字列パラメータ |
failOnStatusCode |
false にすると4xx/5xxでもテストを即失敗させない |
ネガティブテストでは failOnStatusCode: false が重要です。デフォルトでは、cy.request() は2xxまたは3xx以外のステータスで失敗します。
cy.request({
method: 'GET',
url: 'https://jsonplaceholder.typicode.com/users/99999',
failOnStatusCode: false
}).then((response) => {
expect(response.status).to.eq(404)
})
ステータスとボディをアサートする
cy.request() はレスポンスオブジェクトを返します。.then() で受け取り、必要な値を検証します。
よく使うプロパティは次の4つです。
| プロパティ | 内容 |
|---|---|
status |
HTTPステータスコード |
body |
レスポンスボディ。JSONの場合はJavaScriptオブジェクトとして扱える |
headers |
レスポンスヘッダー |
duration |
リクエストにかかった時間。ミリ秒単位 |
基本的なGET APIテストは次のように書けます。
describe('Users API', () => {
it('returns a list of users', () => {
cy.request('https://jsonplaceholder.typicode.com/users').then((response) => {
expect(response.status).to.eq(200)
expect(response.body).to.be.an('array')
expect(response.body).to.have.length(10)
expect(response.body[0]).to.have.property('email')
})
})
})
response.body はJSONとしてパース済みなので、通常のChaiアサーションをそのまま使えます。
例:
expect(response.body).to.be.an('array')
expect(response.body[0]).to.have.property('id')
expect(response.body[0].email).to.include('@')
レスポンスヘッダーも検証できます。
cy.request('https://jsonplaceholder.typicode.com/users').then((response) => {
expect(response.headers['content-type']).to.include('application/json')
})
APIレスポンスの検証設計については、APIアサーションとAPIテストのベストプラクティスも参考になります。
リクエストをチェインして認証トークンを再利用する
実際のAPIでは、ログインしてトークンを取得し、そのトークンを後続リクエストに渡すパターンがよくあります。
Cypressでは cy.request() をチェインして実装できます。
describe('Authenticated API flow', () => {
it('logs in and fetches a protected resource', () => {
cy.request({
method: 'POST',
url: 'https://api.example.com/login',
body: {
email: 'user@example.com',
password: 'secret'
}
}).then((loginResponse) => {
expect(loginResponse.status).to.eq(200)
const token = loginResponse.body.token
cy.request({
method: 'GET',
url: 'https://api.example.com/profile',
headers: {
Authorization: `Bearer ${token}`
}
}).then((profileResponse) => {
expect(profileResponse.status).to.eq(200)
expect(profileResponse.body).to.have.property('email', 'user@example.com')
})
})
})
})
複数のテストで同じ認証処理を使う場合は、beforeEach に切り出します。
describe('Authenticated API tests', () => {
beforeEach(() => {
cy.request({
method: 'POST',
url: 'https://api.example.com/login',
body: {
email: 'user@example.com',
password: 'secret'
}
}).then((response) => {
Cypress.env('authToken', response.body.token)
})
})
it('fetches profile', () => {
cy.request({
method: 'GET',
url: 'https://api.example.com/profile',
headers: {
Authorization: `Bearer ${Cypress.env('authToken')}`
}
}).then((response) => {
expect(response.status).to.eq(200)
})
})
})
この「ログインして、その結果を次のリクエストで使う」構成は、複数APIをまたぐAPI統合テストでもよく使うパターンです。
cy.intercept() でブラウザ通信をスタブ化する
cy.request() は実際のAPIへリクエストを送ります。一方、フロントエンドアプリがブラウザから送るリクエストを監視・差し替えたい場合は cy.intercept() を使います。
重要な違いは次のとおりです。
-
cy.request()はCypressのNodeプロセスからHTTPリクエストを送る -
cy.intercept()はブラウザ上のアプリが送るリクエストを捕捉する -
cy.intercept()はcy.request()を捕捉しない
つまり、API自体をテストしたいなら cy.request()、UIがAPIレスポンスにどう反応するかをテストしたいなら cy.intercept() を使います。
固定レスポンスを返す例です。
cy.intercept('GET', '/api/users', {
statusCode: 200,
body: [{ id: 1, name: 'Ada' }]
})
これで、ブラウザから /api/users に送られるリクエストは、実際のバックエンドではなく上記の固定レスポンスを受け取ります。
これにより、次のようなケースを再現できます。
- 空の一覧
-
500エラー - 遅いレスポンス
- 特定フィールドが欠落したレスポンス
- 権限エラー
リクエストが発生したことを確認するには、.as() でエイリアスを付けて cy.wait() します。
it('shows an error banner when the API fails', () => {
cy.intercept('GET', '/api/users', {
statusCode: 500,
body: { message: 'Server error' }
}).as('getUsers')
cy.visit('/dashboard')
cy.wait('@getUsers')
.its('response.statusCode')
.should('eq', 500)
cy.contains('Something went wrong').should('be.visible')
})
ポイントは、リクエストを発生させる操作より前に cy.intercept() を設定することです。後から設定すると捕捉できません。
レスポンスをスタブすべきか、サービス全体をモックすべきかを整理したい場合は、APIモックとAPIスタブ vs APIモックも参考になります。
APIテストにCypressが適している場面
Cypressは、UIテストスイートを補助するAPIチェックに向いています。
たとえば次の用途です。
- UIテスト前にテストデータを作成する
- ログイン処理をAPIで済ませる
- UIが依存するエンドポイントの最低限の疎通を確認する
- エラー時のUI表示を
cy.intercept()で検証する - 既存のCypress CIに軽量なAPIスモークテストを追加する
このようなケースでは、cy.request() と cy.intercept() を使うことで、テストコードとCIを一箇所にまとめられます。
Cypressが適さない場面
大規模なスタンドアロンAPIテストスイートでは、Cypressだけで運用するのが不便になることがあります。
主な理由は次のとおりです。
- 視覚的なリクエストビルダーがない
- リクエスト、ヘッダー、ボディ、クエリをすべてコードで管理する必要がある
- API専用CIでもCypressの実行コンテキストが必要になる
- API定義、ドキュメント、テストをチームで共有しづらい
- 数百のエンドポイントを扱うと、テストコードの保守コストが増える
APIテストが主作業になる場合は、APIネイティブツールの方が扱いやすくなります。
ApidogはAPI自体を中心に設計されたツールです。エンドポイントを定義し、リクエスト、環境、認証、テストシナリオを共有ワークスペースで管理できます。一般的なケースでは、視覚的なリクエストビルダーとアサーションを使ってテストを構築できます。
CIでは、Apidog CLIで保存済みのテストシナリオをヘッドレス実行できます。
npm install -g apidog-cli
apidog run \
--access-token "$APIDOG_ACCESS_TOKEN" \
-t <scenarioOrSuiteId> \
-e <environmentId> \
-r cli,html,junit
各オプションの役割は次のとおりです。
| オプション | 内容 |
|---|---|
--access-token |
Apidogのアクセストークン |
-t |
実行するシナリオまたはスイートID |
-e |
使用する環境ID |
-r |
レポート形式。cli、html、json、junit など |
-d / --iteration-data
|
データファイルを使った反復実行 |
--upload-report |
結果をワークスペースへアップロード |
JUnit出力は、多くのCIダッシュボードにそのまま取り込めます。なお、Apidog CLIは保存済みシナリオを実行するためのものであり、インタラクティブなリクエスト送信ツールではありません。
判断基準はシンプルです。
- ブラウザテストを支えるAPIチェックには
cy.request() - UIレスポンス制御には
cy.intercept() - APIテストが中心ならAPIネイティブツール
比較検討する場合は、CI/CDで実行されるAPIテスト自動化ツールと30のベストAPIテストツールも参考になります。
FAQ
cy.request() はブラウザで実行されますか?
いいえ。cy.request() はブラウザの外、CypressのNodeプロセスから実行されます。そのため、CORSや同一オリジンポリシーの影響を受けません。また、cy.intercept() が cy.request() を捕捉できない理由でもあります。
ブラウザが送るリクエストを検証したい場合は、アプリの操作でリクエストを発生させ、cy.intercept() で捕捉します。
テストを失敗させずに400や404を検証するには?
failOnStatusCode: false を指定します。
cy.request({
method: 'GET',
url: 'https://api.example.com/items/not-found',
failOnStatusCode: false
}).then((response) => {
expect(response.status).to.eq(404)
})
デフォルトでは、cy.request() は2xxまたは3xx以外のステータスでテストを失敗させます。
UIテスト前に cy.request() でログインできますか?
はい。よく使われるパターンです。
ログインAPIへ POST し、response.body からトークンを取得して、Cypress.env()、localStorage、またはクッキーに保存します。これにより、ログインフォーム操作を省略し、UIテストを認証済み状態から開始できます。
cy.request() と cy.intercept() の違いは?
cy.request() は実際のHTTPリクエストを送信して、実際のレスポンスを取得します。API自体の検証に使います。
cy.intercept() は、フロントエンドがブラウザで送るリクエストを監視または差し替えます。UIが特定のAPIレスポンスにどう反応するかを確認するために使います。
APIテストにはCypressと専用ツールのどちらを使うべきですか?
既存のCypressブラウザテストを補助するAPIチェックならCypressで十分です。
一方で、大規模なAPIテストスイート、CI専用のAPIパイプライン、共有API定義、チームでのテスト管理が必要な場合は、ApidogのようなAPIネイティブツールが適しています。テスト設計の考え方は、APIテストのベストプラクティスも参考になります。
Top comments (0)