# Nodejs Test Strategy

> Define and implement a complete test strategy for Node.js/Next.js/NestJS projects. Use when the user asks for a test pyramid, complete test suites (unit, integration, E2E), coverage thresholds/CI gates, performance testing (k6/Artillery), security (OWASP ZAP), accessibility (axe-core), or mentions tools like Jest, Supertest, Cypress, Playwright, or Pact.

- Skill: `lucasaero-pr/nodejs-test-strategy` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add lucasaero-pr/nodejs-test-strategy`
- Raw SKILL.md: https://api.skillmd.com/api/skills/lucasaero-pr/nodejs-test-strategy/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: LucasAero-pr (https://skillmd.com/u/lucasaero-pr)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/lucasaero-pr/nodejs-test-strategy

---


# Node.js Test Strategy

## Overview

When activated, deliver a structured, production-grade test strategy tailored to the project's stack (Node.js, Next.js, and/or NestJS). Cover all pyramid layers, tooling, coverage gates, CI/CD integration, and non-functional testing.

---

## Step 1: Identify the Project Stack

Before generating anything, inspect the project to determine:

- **Runtime**: Node.js / Next.js (App Router or Pages) / NestJS
- **Existing test tooling**: check `package.json` for jest, vitest, cypress, playwright, supertest, etc.
- **Monorepo or single package**: check for `turbo.json`, `nx.json`, `pnpm-workspace.yaml`, `lerna.json`
- **Database/ORM**: Prisma, TypeORM, Mongoose — affects integration test setup
- **Auth layer**: JWT, NextAuth, Passport — requires security-specific tests

Use this context to tailor every recommendation below.

---

## Step 2: Test Pyramid — Layers and Tools

### Layer 1 — Unit Tests (Jest)

**Coverage target: 90%+ (lines, branches, functions)**

Tools: `jest`, `ts-jest` or `@swc/jest`, `@faker-js/faker`, custom matchers

Key patterns:
- Test pure functions, services, utilities, guards, pipes, DTOs in isolation
- Mock all external dependencies (DB, HTTP, file system) with `jest.mock()` or manual mocks
- Use `describe` / `it` blocks with AAA pattern (Arrange, Act, Assert)
- Add custom matchers in `jest.setup.ts` for domain assertions (e.g., `toBeValidUUID`, `toMatchApiShape`)

**NestJS unit example:**
```typescript
// src/users/users.service.spec.ts
import { Test } from '@nestjs/testing';
import { UsersService } from './users.service';
import { getRepositoryToken } from '@nestjs/typeorm';
import { User } from './user.entity';

describe('UsersService', () => {
  let service: UsersService;
  const mockRepo = { findOne: jest.fn(), save: jest.fn() };

  beforeEach(async () => {
    const module = await Test.createTestingModule({
      providers: [
        UsersService,
        { provide: getRepositoryToken(User), useValue: mockRepo },
      ],
    }).compile();
    service = module.get(UsersService);
  });

  it('should find a user by id', async () => {
    mockRepo.findOne.mockResolvedValue({ id: '1', email: 'a@b.com' });
    const user = await service.findById('1');
    expect(user).toMatchObject({ email: 'a@b.com' });
  });
});
```

**Next.js unit example (React Testing Library):**
```typescript
// components/Button/Button.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { Button } from './Button';

describe('Button', () => {
  it('calls onClick when clicked', () => {
    const handler = jest.fn();
    render(<Button onClick={handler}>Submit</Button>);
    fireEvent.click(screen.getByRole('button', { name: /submit/i }));
    expect(handler).toHaveBeenCalledTimes(1);
  });
});
```

---

### Layer 2 — Integration Tests (Jest + Supertest)

**Coverage target: 80%+ of API routes and DB interactions**

Tools: `supertest`, `@nestjs/testing`, `testcontainers` or in-memory DB

Key patterns:
- Spin up the real NestJS app or Next.js API handler with a test database
- Test the full request/response cycle including middleware, guards, interceptors
- Use `beforeAll` to seed data; `afterAll` to teardown
- Prefer `testcontainers` (real Postgres/Redis in Docker) for critical data paths

**NestJS integration example:**
```typescript
// test/users.e2e-spec.ts (integration layer)
import * as request from 'supertest';
import { Test } from '@nestjs/testing';
import { AppModule } from '../src/app.module';
import { INestApplication } from '@nestjs/common';

describe('Users API', () => {
  let app: INestApplication;

  beforeAll(async () => {
    const module = await Test.createTestingModule({ imports: [AppModule] }).compile();
    app = module.createNestApplication();
    await app.init();
  });

  afterAll(() => app.close());

  it('GET /users/:id returns 200 for existing user', () => {
    return request(app.getHttpServer())
      .get('/users/1')
      .expect(200)
      .expect(res => expect(res.body).toHaveProperty('email'));
  });
});
```

**Contract testing (Pact) — for microservices:**
```typescript
// When services communicate over HTTP, add Pact consumer/provider tests
// Consumer defines the contract; provider verifies it in CI
```

---

### Layer 3 — E2E Tests (Cypress or Playwright)

**Coverage target: 100% of critical user paths**

Tools: `cypress` (component + E2E) or `playwright` (cross-browser, recommended for Next.js)

Critical paths to always cover:
- Authentication flow (sign-up, login, logout, session expiry)
- Core business flow (checkout, form submission, dashboard load)
- Error states (404, 500, validation errors)
- Accessibility via `@axe-core/playwright` or `cypress-axe`

**Playwright example:**
```typescript
// e2e/auth.spec.ts
import { test, expect } from '@playwright/test';

test('user can log in and see dashboard', async ({ page }) => {
  await page.goto('/login');
  await page.fill('[name=email]', 'user@test.com');
  await page.fill('[name=password]', 'secret');
  await page.click('[type=submit]');
  await expect(page).toHaveURL('/dashboard');
  await expect(page.locator('h1')).toContainText('Welcome');
});
```

---

## Step 3: Coverage Configuration

Generate or update `jest.config.ts` with coverage thresholds:

```typescript
// jest.config.ts
import type { Config } from 'jest';

const config: Config = {
  preset: 'ts-jest',
  testEnvironment: 'node',  // use 'jsdom' for Next.js components
  collectCoverageFrom: ['src/**/*.ts', '!src/**/*.spec.ts', '!src/main.ts'],
  coverageThresholds: {
    global: {
      lines: 90,
      branches: 85,
      functions: 90,
      statements: 90,
    },
  },
  coverageReporters: ['text', 'lcov', 'html'],
};
export default config;
```

---

## Step 4: Performance Testing (k6 / Artillery)

**Trigger**: user mentions load testing, performance budgets, or SLA targets.

Tools: `k6` (recommended) or `artillery`

**k6 smoke + load script:**
```javascript
// tests/performance/load.k6.js
import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  stages: [
    { duration: '1m', target: 20 },   // ramp up
    { duration: '3m', target: 20 },   // sustained load
    { duration: '1m', target: 0 },    // ramp down
  ],
  thresholds: {
    http_req_duration: ['p(95)<500'],  // 95th percentile < 500ms
    http_req_failed: ['rate<0.01'],    // < 1% error rate
  },
};

export default function () {
  const res = http.get('https://staging.example.com/api/products');
  check(res, { 'status 200': r => r.status === 200 });
  sleep(1);
}
```

Run: `k6 run tests/performance/load.k6.js`

---

## Step 5: Security Testing (OWASP ZAP)

**Trigger**: user asks about security tests, OWASP, API security, or pen testing.

Strategy:
- Use **ZAP Baseline Scan** in CI for passive scanning (no active attacks)
- Use **ZAP Full Scan** for staging environments only
- Supplement with `npm audit` and `snyk` for dependency scanning

**CI step (GitHub Actions):**
```yaml
- name: OWASP ZAP Baseline Scan
  uses: zaproxy/action-baseline@v0.10.0
  with:
    target: 'https://staging.example.com'
    rules_file_name: '.zap/rules.tsv'
    fail_action: true
```

---

## Step 6: Accessibility Testing (axe-core)

**Trigger**: user mentions a11y, WCAG, accessibility audits, or screen readers.

Tools: `@axe-core/playwright`, `cypress-axe`, `jest-axe`

**Playwright a11y example:**
```typescript
// e2e/accessibility.spec.ts
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';

test('homepage has no critical a11y violations', async ({ page }) => {
  await page.goto('/');
  const results = await new AxeBuilder({ page })
    .withTags(['wcag2a', 'wcag2aa'])
    .analyze();
  expect(results.violations).toHaveLength(0);
});
```

---

## Step 7: CI/CD Quality Gates

Generate a `.github/workflows/test.yml` (or equivalent) with all gates:

```yaml
name: Test & Quality Gates

on: [push, pull_request]

jobs:
  unit-and-integration:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '20', cache: 'npm' }
      - run: npm ci
      - run: npm run test:ci  # jest --coverage --ci
      - name: Upload coverage
        uses: codecov/codecov-action@v4

  e2e:
    runs-on: ubuntu-latest
    needs: unit-and-integration
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '20', cache: 'npm' }
      - run: npm ci
      - run: npx playwright install --with-deps
      - run: npm run test:e2e

  security:
    runs-on: ubuntu-latest
    needs: unit-and-integration
    steps:
      - run: npm audit --audit-level=high

  performance:
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
      - uses: actions/checkout@v4
      - run: |
          curl -L https://github.com/grafana/k6/releases/download/v0.52.0/k6-v0.52.0-linux-amd64.tar.gz | tar xz
          ./k6-v0.52.0-linux-amd64/k6 run tests/performance/load.k6.js
```

**`package.json` test scripts:**
```json
{
  "scripts": {
    "test": "jest",
    "test:ci": "jest --coverage --ci --runInBand",
    "test:watch": "jest --watch",
    "test:e2e": "playwright test",
    "test:perf": "k6 run tests/performance/load.k6.js"
  }
}
```

---

## Step 8: Test Maintenance Guidelines

Enforce these rules when generating or reviewing tests:

1. **Fast**: unit tests must complete in < 100ms each; use mocks aggressively
2. **Independent**: no shared mutable state between tests; each test sets up its own data
3. **Deterministic**: never use real timers (`Date.now()`, `setTimeout`) — mock them with `jest.useFakeTimers()`
4. **No false positives**: avoid `expect(wrapper).toBeDefined()` — assert on actual behavior
5. **Custom matchers**: add domain-specific matchers to `jest.setup.ts` to reduce boilerplate and improve readability
6. **Test naming**: `it('should [expected behavior] when [condition]')` pattern
7. **No implementation details**: for React, prefer `getByRole`/`getByLabelText` over test IDs; for services, assert outputs not internal calls

---

## Output Format

When delivering a test strategy, structure the response as:

1. **Stack summary** — what was detected
2. **Pyramid overview** — which layers apply and why
3. **Ready-to-use code** — paste-ready test files and configs
4. **Coverage targets table** — per-layer goals
5. **CI/CD snippet** — copy-paste workflow
6. **Next steps** — prioritized list of what to implement first

