콘텐츠로 이동

SuperTest 치트시트

SuperTest 치트시트

설치

플랫폼명령어
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

기본 HTTP 메서드

명령어설명
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((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 기본 인증 설정
.field('name', 'value')다중 부분(multipart) 양식 필드 추가
.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)지속적인 쿠키를 위한 에이전트 생성
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)테스트 프레임워크로 응답 본문에 대해 어설트(Assert)하기
expect(res.headers['x-custom']).toBe('value')응답 헤더에 대해 어설트(Assert)하기
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 };

Jest 구성

// jest.config.js
module.exports = {
  testEnvironment: 'node',
  testMatch: ['**/__tests__/**/*.test.js'],
  collectCoverageFrom: ['src/**/*.js'],
  coveragePathIgnorePatterns: ['/node_modules/']
};

Mocha 구성

// test/mocha.opts
--require test/setup.js
--recursive
--timeout 5000
--exit

Package.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
};

일반적인 사용 사례

사용 사례 1: REST API CRUD 작업 테스트

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);
  });
});

사용 사례 2: 인증 흐름 테스트

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);
  });
});

사용 사례 3: 파일 업로드 테스트

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);
      });
  });
});

사용 사례 4: 지속적인 세션으로 테스트

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);
  });
});

사용 사례 5: 오류 처리 테스트

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');
      });
  });
});

모범 사례

  • async/await 구문 사용: 콜백보다 더 깔끔하고 가독성이 높습니다. 최신 JavaScript는 이 패턴을 기본적으로 지원합니다.

    const res = await request(app).get('/api/users').expect(200);
  • 개발 의존성으로 설치: SuperTest는 테스트 도구이므로 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.