コンテンツにスキップ

Supertest.Ja

プラットフォームコマンド
npm (All Platforms)npm install supertest --save-dev
Yarn (All Platforms)yarn add supertest --dev
With Jestnpm install jest supertest --save-dev
With Mochanpm install mocha supertest --save-dev
With TypeScriptnpm install supertest @types/supertest --save-dev
Specific Versionnpm install supertest@6.3.3 --save-dev
コマンド説明
request(app).get('/api/users')エンドポイントにGETリクエストを送信
request(app).post('/api/users')エンドポイントにPOSTリクエストを送信
request(app).put('/api/users/123')リソースを更新するPUT要求を送信
request(app).patch('/api/users/123')部分更新のためにPATCHリクエストを送信
request(app).delete('/api/users/123')リソースを削除するためにDELETE リクエストを送信
request(app).head('/api/users')HEADリクエストを送信(ヘッダーのみ)
request(app).options('/api/users')CORSプリフライトのためのOPTIONSリクエストを送信
.send({ name: 'John' })リクエストボディにJSONデータを送信
.query({ page: 1, limit: 10 })URLにクエリパラメータを追加する
.set('Authorization', 'Bearer token')リクエストヘッダーを設定する
.expect(200)HTTPステータスコードを確認する
.expect('Content-Type', /json/)レスポンスヘッダーの値をアサートする
.end((err, res) => {})コールバック付きでリクエストを実行
await request(app).get('/api/users')Promise/async-awaitでリクエストを実行する
コマンド説明
.expect(200)正確なステータスコードを期待する
.expect(200, { id: 1 })ステータスと完全な本文の一致を期待する
.expect('Content-Type', 'application/json')正確なヘッダー値を期待する
.expect('Content-Type', /json/)Expect ヘッダーマッチング regex
.expect((res) => { if (!res.body) throw new Error() })カスタムアサーション関数
.expect(200, /success/)ステータスとボディが正規表現にマッチすることを期待する
.expect('Location', '/new-url')リダイレクトロケーションヘッダーを予期する
.expect({ error: 'Not found' })正確な JSON 本文の一致を期待する
コマンド説明
.set('Authorization', 'Bearer token')単一のリクエストヘッダーを設定する
.set({ 'X-API-Key': 'key', 'Accept': 'json' })複数のヘッダーを設定
.auth('username', 'password')HTTP Basic 認証を設定する
.field('name', 'value')フォームフィールドを追加(マルチパート)
.attach('file', 'path/to/file.pdf')アップロード用のファイルを添付
.attach('file', buffer, 'filename.pdf')バッファからファイルを添付
.timeout(5000)リクエストのタイムアウトをミリ秒で設定
.redirects(5)最大5つのリダイレクトに従う
.type('json')Content-Typeヘッダーを設定する
.accept('json')Accept ヘッダーを設定
.send('raw string data')生の文字列本文を送信
.send(Buffer.from('data'))バイナリデータを送信
コマンド説明
request.agent(app)永続的なCookieのためのエージェントを作成する
agent.get('/api/profile')認証されたリクエストにエージェントを使用する
request('https://api.example.com')外部APIエンドポイントをテストする
.parse((res, callback) => {})カスタムレスポンスパーサー
.buffer(true)バッファレスポンスボディ
.responseType('blob')予想される応答タイプを設定
.retry(3)失敗したリクエストを3回再試行する
.ca(cert)カスタム CA 証明書を設定
.cert(cert)クライアント証明書を設定
.key(key)クライアントの秘密鍵を設定
.pfx(pfxData)PFX/PKCS12 証明書を設定する
.disableTLSCerts()TLS 証明書の検証を無効にする
パターン説明
const res = await request(app).get('/api/users')複数のアサーションのレスポンスを保存
expect(res.body).toHaveLength(10)テストフレームワークでレスポンスボディをアサートする
expect(res.headers['x-custom']).toBe('value')レスポンスヘッダーでアサートする
expect(res.status).toBe(200)代替ステータスアサーション
Promise.all([request(app).get('/a'), request(app).get('/b')])並列リクエストテスト
for (const user of users) { await request(app).post('/api/users').send(user) }連続テストリクエスト
// test/setup.js
const request = require('supertest');
const app = require('../app');

module.exports = { request, app };
```## テストパターン
```javascript
// jest.config.js
module.exports = {
  testEnvironment: 'node',
  testMatch: ['**/__tests__/**/*.test.js'],
  collectCoverageFrom: ['src/**/*.js'],
  coveragePathIgnorePatterns: ['/node_modules/']
};
```## 設定
```javascript
// test/mocha.opts
--require test/setup.js
--recursive
--timeout 5000
--exit
```### 基本的なテストセットアップ
```json
{
  "scripts": {
    "test": "jest",
    "test:watch": "jest --watch",
    "test:coverage": "jest --coverage",
    "test:api": "jest --testPathPattern=api"
  }
}
// config/test.js
module.exports = {
  port: process.env.TEST_PORT || 3001,
  database: process.env.TEST_DB || 'test_db',
  apiTimeout: process.env.API_TIMEOUT || 5000
};
```### Jestの設定
```javascript
const request = require('supertest');
const app = require('../app');

describe('User API', () => {
  let userId;

  // Create
  test('POST /api/users - create user', async () => {
    const res = await request(app)
      .post('/api/users')
      .send({ name: 'John Doe', email: 'john@example.com' })
      .expect(201)
      .expect('Content-Type', /json/);
    
    userId = res.body.id;
    expect(res.body.name).toBe('John Doe');
  });

  // Read
  test('GET /api/users/:id - get user', async () => {
    await request(app)
      .get(`/api/users/${userId}`)
      .expect(200)
      .expect((res) => {
        expect(res.body.name).toBe('John Doe');
      });
  });

  // Update
  test('PUT /api/users/:id - update user', async () => {
    await request(app)
      .put(`/api/users/${userId}`)
      .send({ name: 'Jane Doe' })
      .expect(200);
  });

  // Delete
  test('DELETE /api/users/:id - delete user', async () => {
    await request(app)
      .delete(`/api/users/${userId}`)
      .expect(204);
  });
});
const request = require('supertest');
const app = require('../app');

describe('Authentication', () => {
  let authToken;

  test('POST /auth/register - register new user', async () => {
    await request(app)
      .post('/auth/register')
      .send({
        email: 'test@example.com',
        password: 'SecurePass123!',
        name: 'Test User'
      })
      .expect(201);
  });

  test('POST /auth/login - login user', async () => {
    const res = await request(app)
      .post('/auth/login')
      .send({
        email: 'test@example.com',
        password: 'SecurePass123!'
      })
      .expect(200)
      .expect('Content-Type', /json/);
    
    authToken = res.body.token;
    expect(authToken).toBeDefined();
  });

  test('GET /api/profile - access protected route', async () => {
    await request(app)
      .get('/api/profile')
      .set('Authorization', `Bearer ${authToken}`)
      .expect(200)
      .expect((res) => {
        expect(res.body.email).toBe('test@example.com');
      });
  });

  test('GET /api/profile - reject without token', async () => {
    await request(app)
      .get('/api/profile')
      .expect(401);
  });
});
```### Mochaの設定
```javascript
const request = require('supertest');
const app = require('../app');
const path = require('path');

describe('File Upload', () => {
  test('POST /api/upload - upload single file', async () => {
    const filePath = path.join(__dirname, 'fixtures', 'test.pdf');
    
    const res = await request(app)
      .post('/api/upload')
      .attach('document', filePath)
      .field('title', 'Test Document')
      .field('description', 'A test file upload')
      .expect(200)
      .expect('Content-Type', /json/);
    
    expect(res.body.filename).toMatch(/\.pdf$/);
    expect(res.body.size).toBeGreaterThan(0);
  });

  test('POST /api/upload - upload multiple files', async () => {
    await request(app)
      .post('/api/upload/multiple')
      .attach('files', 'test/fixtures/file1.pdf')
      .attach('files', 'test/fixtures/file2.pdf')
      .expect(200)
      .expect((res) => {
        expect(res.body.files).toHaveLength(2);
      });
  });
});
const request = require('supertest');
const app = require('../app');

describe('Session Management', () => {
  const agent = request.agent(app);

  test('Login and maintain session', async () => {
    // Login
    await agent
      .post('/auth/login')
      .send({ username: 'user', password: 'pass' })
      .expect(200);

    // Session persists - can access protected route
    await agent
      .get('/api/dashboard')
      .expect(200);

    // Session still valid for subsequent requests
    await agent
      .get('/api/profile')
      .expect(200);

    // Logout
    await agent
      .post('/auth/logout')
      .expect(200);

    // Session expired - access denied
    await agent
      .get('/api/dashboard')
      .expect(401);
  });
});
```### package.jsonスクリプト
```javascript
const request = require('supertest');
const app = require('../app');

describe('Error Handling', () => {
  test('404 - Resource not found', async () => {
    await request(app)
      .get('/api/users/99999')
      .expect(404)
      .expect((res) => {
        expect(res.body.error).toBe('User not found');
        expect(res.body.code).toBe('USER_NOT_FOUND');
      });
  });

  test('400 - Validation error', async () => {
    await request(app)
      .post('/api/users')
      .send({ name: '' }) // Invalid data
      .expect(400)
      .expect((res) => {
        expect(res.body.errors).toBeDefined();
        expect(res.body.errors.name).toContain('required');
      });
  });

  test('429 - Rate limit exceeded', async () => {
    // Make multiple requests to trigger rate limit
    const requests = Array(101).fill().map(() =>
      request(app).get('/api/users')
    );
    
    const responses = await Promise.all(requests);
    const rateLimited = responses.find(r => r.status === 429);
    
    expect(rateLimited).toBeDefined();
    expect(rateLimited.body.error).toMatch(/rate limit/i);
  });

  test('500 - Internal server error', async () => {
    await request(app)
      .get('/api/trigger-error')
      .expect(500)
      .expect((res) => {
        expect(res.body.error).toBe('Internal server error');
      });
  });
});
  const res = await request(app).get('/api/users').expect(200);
  ```### 環境変数
`devDependencies`
```bash
  npm install supertest --save-dev
  • Don’t start the server manually: SuperTest handles server binding automatically. Pass the Express app directly without calling app.listen().

    // Good: Export app without listening
    module.exports = app;
    
    // Bad: Don't do this in test files
    app.listen(3000);
  • Use request.agent() for session testing: When testing authenticated routes or cookie-based sessions, create an agent to persist cookies across requests.

    const agent = request.agent(app);
    await agent.post('/login').send(credentials);
    await agent.get('/protected'); // Cookies persist
  • Chain expectations for cleaner tests: Multiple .expect() calls can be chained for comprehensive assertions in a single test.

    await request(app)
      .get('/api/users')
      .expect(200)
      .expect('Content-Type', /json/)
      .expect((res) => expect(res.body).toHaveLength(10));
  • Store responses for complex assertions: Capture the response object when you need to perform multiple assertions on different parts.

    const res = await request(app).get('/api/users').expect(200);
    expect(res.body).toHaveLength(5);
    expect(res.headers['x-total-count']).toBe('100');
  • Test both success and failure cases: Always test error scenarios, validation failures, and edge cases alongside happy paths.

    test('returns 404 for non-existent user', async () => {
      await request(app).get('/api/users/99999').expect(404);
    });
  • Use descriptive test names: Write test descriptions that clearly explain what behavior is being verified.

    test('POST /api/users returns 400 when email is missing', async () => {
      // Test implementation
    });
  • Clean up test data: Use beforeEach/afterEach hooks to reset database state and ensure test isolation.

    afterEach(async () => {
      await User.deleteMany({});
    });
  • Set appropriate timeouts: For slow endpoints or external API calls, configure timeout values to prevent false failures.

    await request(app).get('/api/slow').timeout(10000).expect(200);

Troubleshooting

問題ソリューション
EADDRINUSE: address already in useDon’t call app.listen() in your app file when testing. Export the app without starting the server, or use different ports for testing.
Tests hang and never completeEnsure database connections are closed after tests. Use afterAll() to close connections: afterAll(() => mongoose.connection.close())
Cannot read property 'body' of undefinedMissing await keyword or .end() callback. Always use await with async/await or provide .end() callback.
Headers already sent errorYou’re sending multiple responses in your route handler. Ensure only one res.send(), res.json(), or similar call per request.
Timeout errors on slow endpointsIncrease timeout with .timeout(ms) or adjust global timeout in test framework config: jest.setTimeout(10000)
Authentication tests fail randomlyUse request.agent() to persist cookies/sessions across requests instead of separate request() calls.
File upload tests failVerify file path is correct using path.join(__dirname, 'fixtures', 'file.pdf'). Ensure multipart middleware is configured in app.
CORS errors in testsCORS is typically a browser concern. SuperTest bypasses CORS. If testing CORS headers, use .options() request with Origin header.
SSL/TLS certificate errorsUse .disableTLSCerts() for testing environments or provide valid certificates with .ca(), .cert(), .key() methods.
Response body is empty BufferResponse might be binary. Use .buffer(true) or check Content-Type. For JSON, ensure server sends correct Content-Type header.
Tests pass individually but fail togetherTests aren’t isolated. Clear database/state between tests using beforeEach/afterEach hooks. Check for shared mutable state.