supertest の使い方

SuperAgent driven library for testing HTTP servers

v7.2.2/週MITテスト
AI生成コンテンツ

この記事はAIによって生成されました。内容の正確性は保証されません。最新の情報は公式ドキュメントをご確認ください。

supertest の使い方 — Node.js HTTPサーバーのテストを簡潔に書く

一言でいうと

supertest は、Express などの Node.js HTTPサーバーに対して、リクエスト送信とレスポンス検証を一連のチェーンで記述できるテストライブラリです。サーバーを実際に起動せずとも、アプリケーションインスタンスを渡すだけでテストが実行できます。

どんな時に使う?

  • Express / Koa / Fastify などの REST API のエンドポイントテスト — ステータスコード、レスポンスボディ、ヘッダーをまとめて検証したいとき
  • 認証付きAPIのテスト — Cookie やセッションの永続化が必要なフローを agent で再現したいとき
  • CI/CD パイプラインでの統合テスト — Jest や Mocha と組み合わせて、HTTPレイヤーの自動テストを回したいとき

インストール

# npm
npm install supertest --save-dev

# yarn
yarn add -D supertest

# pnpm
pnpm add -D supertest

TypeScript を使う場合は型定義も追加します。

npm install @types/supertest --save-dev

基本的な使い方

Express アプリを Jest でテストする最も典型的なパターンです。

// app.ts
import express, { Request, Response } from 'express';

const app = express();
app.use(express.json());

app.get('/users/:id', (req: Request, res: Response) => {
  res.status(200).json({ id: req.params.id, name: 'Alice' });
});

export default app;
// app.test.ts
import request from 'supertest';
import app from './app';

describe('GET /users/:id', () => {
  it('ユーザー情報をJSONで返す', async () => {
    const response = await request(app)
      .get('/users/1')
      .set('Accept', 'application/json')
      .expect('Content-Type', /json/)
      .expect(200);

    expect(response.body).toEqual({ id: '1', name: 'Alice' });
  });
});

ポイントは request(app) にExpressアプリのインスタンスをそのまま渡している点です。supertest が内部で一時的なポートにバインドしてくれるため、app.listen() を呼ぶ必要がありません。

よく使うAPI

1. request(app).get() / .post() / .put() / .delete() — HTTPメソッドの指定

// GET
await request(app).get('/users').expect(200);

// POST(JSONボディ付き)
await request(app)
  .post('/users')
  .send({ name: 'Bob', email: 'bob@example.com' })
  .expect(201);

// PUT
await request(app)
  .put('/users/1')
  .send({ name: 'Bob Updated' })
  .expect(200);

// DELETE
await request(app).delete('/users/1').expect(204);

2. .expect() — レスポンスのアサーション

.expect() はオーバーロードされており、さまざまな形式で検証できます。

// ステータスコードの検証
await request(app).get('/users').expect(200);

// ヘッダーの検証(文字列一致)
await request(app).get('/users').expect('Content-Type', 'application/json; charset=utf-8');

// ヘッダーの検証(正規表現)
await request(app).get('/users').expect('Content-Type', /json/);

// レスポンスボディの検証(ステータスコードとボディを同時に)
await request(app).get('/users/1').expect(200, { id: '1', name: 'Alice' });

// カスタムアサーション関数
await request(app)
  .get('/users')
  .expect((res) => {
    if (!Array.isArray(res.body)) {
      throw new Error('レスポンスが配列ではありません');
    }
  });

3. .set() — リクエストヘッダーの設定

await request(app)
  .get('/protected')
  .set('Authorization', 'Bearer eyJhbGciOiJIUzI1NiIs...')
  .set('Accept', 'application/json')
  .expect(200);

// オブジェクトで一括設定も可能
await request(app)
  .get('/protected')
  .set({
    Authorization: 'Bearer eyJhbGciOiJIUzI1NiIs...',
    Accept: 'application/json',
  })
  .expect(200);

4. request.agent(app) — Cookie / セッションの永続化

複数リクエストにまたがってCookieを保持したい場合に使います。

import request from 'supertest';
import app from './app';

describe('認証フロー', () => {
  const agent = request.agent(app);

  it('ログインするとCookieがセットされる', async () => {
    await agent
      .post('/login')
      .send({ email: 'alice@example.com', password: 'secret' })
      .expect(200)
      .expect('set-cookie', /session/);
  });

  it('ログイン後はCookieが自動送信される', async () => {
    // 上のテストでセットされたCookieが自動的に送られる
    await agent
      .get('/dashboard')
      .expect(200);
  });
});

5. .attach() / .field() — ファイルアップロード(multipart)

await request(app)
  .post('/upload')
  .field('description', 'プロフィール画像')
  .field('metadata', JSON.stringify({ width: 200 }), {
    contentType: 'application/json',
  })
  .attach('avatar', 'test/fixtures/avatar.png')
  .expect(200);

6. .auth() — Basic / Bearer 認証

// Basic認証
await request(app)
  .get('/admin')
  .auth('admin', 'password123')
  .expect(200);

// Bearer トークン
await request(app)
  .get('/admin')
  .auth('eyJhbGciOiJIUzI1NiIs...', { type: 'bearer' })
  .expect(200);

類似パッケージとの比較

特徴supertestaxios + テストフレームワークnode-mocks-httppactum
サーバー起動不要✅ アプリインスタンスを渡すだけ❌ 実際にlistenが必要✅ req/resをモック
チェーンAPIでアサーション❌ 自前で書く
Cookie/セッション永続化agentwithCredentials
HTTP/2 対応✅ (v7+)
学習コスト低い低い(既知のHTTPクライアント)低いやや高い
ユースケース統合テストE2Eテストユニットテスト(ミドルウェア単体)API テスト全般

選定の目安: Express/Koa のルーティング〜レスポンスまでを通しでテストしたいなら supertest が最もシンプルです。ミドルウェア単体のユニットテストなら node-mocks-http、より高機能なAPIテストスイートが必要なら pactum を検討してください。

注意点・Tips

.end() と Promise/async-await を混在させない

.expect() は Promise を返します。.end() コールバックと await を混ぜると、テストが意図通りに失敗しないことがあります。

// ❌ BAD: .end() と await の混在
const res = await request(app).get('/users').end((err, res) => { /* ... */ });

// ✅ GOOD: async/await に統一
const response = await request(app).get('/users').expect(200);
expect(response.body).toHaveLength(3);

.end() 使用時はエラーを必ず done() に渡す

.end() を使う場合、.expect() の失敗は例外ではなくコールバックの第一引数に渡されます。これを無視するとテストが常にパスしてしまいます。

// ❌ BAD: エラーを握りつぶしている
.end((err, res) => {
  done();
});

// ✅ GOOD
.end((err, res) => {
  if (err) return done(err);
  done();
});

テスト間でサーバーインスタンスを共有する場合の注意

request(app) は呼び出すたびに一時ポートを確保・解放します。大量のテストを並列実行するとポート枯渇が起きる可能性があります。その場合は request.agent(app) を使うか、テストの並列度を制限してください。

カスタム .expect() でレスポンスを加工してから検証する

.expect() に関数を渡すと、後続の .expect() の前にレスポンスを変換できます。日付やIDなど不定値を固定値に差し替えてからスナップショットテストする際に便利です。

await request(app)
  .post('/users')
  .send({ name: 'Charlie' })
  .expect((res) => {
    // 不定値を固定して後続のアサーションを安定させる
    res.body.id = 'FIXED_ID';
    res.body.createdAt = 'FIXED_DATE';
  })
  .expect(201, {
    id: 'FIXED_ID',
    name: 'Charlie',
    createdAt: 'FIXED_DATE',
  });

HTTP/2 を有効にする(v7+)

// リクエスト単位
request(app, { http2: true }).get('/users').expect(200);

// agent でも同様
request.agent(app, { http2: true }).get('/users').expect(200);

※ HTTP/2 オプションは supertest v7 以降で利用可能です。

まとめ

supertest は、Node.js の HTTPサーバーテストにおけるデファクトスタンダードです。request(app) にアプリインスタンスを渡すだけでポート管理不要のテストが書け、.expect() のチェーンでステータスコード・ヘッダー・ボディを直感的に検証できます。Jest や Mocha など任意のテストフレームワークと組み合わせられるため、Express ベースのプロジェクトであればまず導入を検討すべきライブラリです。