Chuyển đến nội dung chính

第 17 課:測試 Node.js 應用程式

Node.js 內建測試運行器 (node:test)。 Vitest 用於單元/整合測試。用於 HTTP 測試的 Supertest。用於 HTTP 模擬的 Nock。測試容器,程式碼覆蓋率(c8/istanbul)。

💻 程式設計 — 第 17 課 第 17 課:測試 Node.js 應用程式

Node.js 核心:從基礎到高級

第 5 部分:測試、性能和生產

亞洲開發網

1.內建測試運行器(節點:測試)

import { describe, it, before, after, mock } from 'node:test'
import assert from 'node:assert/strict'

describe('UserService', () => {
  let db: Database

  before(async () => {
    db = await Database.connect(':memory:')
    await db.migrate()
  })

  after(async () => {
    await db.close()
  })

  it('creates a user', async () => {
    const user = await createUser(db, { name: 'Test', email: '[email protected]' })
    assert.strictEqual(user.name, 'Test')
    assert.ok(user.id)
  })

  it('rejects duplicate email', async () => {
    await assert.rejects(
      () => createUser(db, { name: 'Test2', email: '[email protected]' }),
      { message: /unique constraint/i }
    )
  })

  it('mocks external service', async () => {
    const sendEmail = mock.fn(async () => ({ success: true }))
    const result = await sendEmail('[email protected]', 'Hello')
    assert.strictEqual(sendEmail.mock.calls.length, 1)
  })
})
node --test --test-reporter spec src/**/*.test.ts

2.維泰斯特

// vitest.config.ts
import { defineConfig } from 'vitest/config'

export default defineConfig({
  test: {
    globals: true,
    coverage: {
      provider: 'v8',
      reporter: ['text', 'html', 'lcov'],
      include: ['src/**'],
      exclude: ['src/**/*.test.ts'],
    },
  },
})
// src/services/user.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest'

describe('UserService', () => {
  const mockDb = {
    query: vi.fn(),
  }

  beforeEach(() => {
    vi.clearAllMocks()
  })

  it('finds user by id', async () => {
    mockDb.query.mockResolvedValue({
      rows: [{ id: '1', name: 'Test' }]
    })

    const user = await findUser(mockDb as any, '1')
    expect(user).toEqual({ id: '1', name: 'Test' })
    expect(mockDb.query).toHaveBeenCalledWith(
      expect.stringContaining('SELECT'),
      ['1']
    )
  })
})

3.HTTP測試(超級測試)

import request from 'supertest'
import { createApp } from '../src/app'

const app = createApp()

describe('API', () => {
  it('GET /api/users returns users', async () => {
    const res = await request(app)
      .get('/api/users')
      .expect(200)
      .expect('Content-Type', /json/)

    expect(res.body).toBeInstanceOf(Array)
    expect(res.body[0]).toHaveProperty('name')
  })

  it('POST /api/users creates user', async () => {
    const res = await request(app)
      .post('/api/users')
      .send({ name: 'New User', email: '[email protected]' })
      .expect(201)

    expect(res.body.name).toBe('New User')
  })

  it('returns 401 without auth', async () => {
    await request(app)
      .get('/api/admin')
      .expect(401)
  })
})

4. HTTP 模擬(Nock)

import nock from 'nock'

it('handles external API', async () => {
  nock('https://api.github.com')
    .get('/users/octocat')
    .reply(200, { login: 'octocat', id: 1 })

  const user = await fetchGitHubUser('octocat')
  expect(user.login).toBe('octocat')
})

5. 測試容器

import { PostgreSqlContainer } from '@testcontainers/postgresql'

let container: any
let connectionString: string

beforeAll(async () => {
  container = await new PostgreSqlContainer().start()
  connectionString = container.getConnectionUri()
  await runMigrations(connectionString)
}, 30000)

afterAll(async () => {
  await container.stop()
})

下一篇: 性能分析和優化 — V8 分析器、火焰圖、Clinic.js。