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);
類似パッケージとの比較
| 特徴 | supertest | axios + テストフレームワーク | node-mocks-http | pactum |
|---|---|---|---|---|
| サーバー起動不要 | ✅ アプリインスタンスを渡すだけ | ❌ 実際にlistenが必要 | ✅ req/resをモック | ✅ |
| チェーンAPIでアサーション | ✅ | ❌ 自前で書く | ❌ | ✅ |
| Cookie/セッション永続化 | ✅ agent | ✅ withCredentials | ❌ | ✅ |
| 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 ベースのプロジェクトであればまず導入を検討すべきライブラリです。