NYC

Testing Code

SKILL.md

Testing Code

Core Workflow

Test writing follows a systematic approach: determine scope, understand patterns, map to requirements, write tests, verify coverage.

1. Determine Test Scope

Read project documentation:

  • docs/user-stories/US-###-*.md for acceptance criteria to test
  • docs/feature-spec/F-##-*.md for technical requirements
  • docs/api-contracts.yaml for API specifications
  • Existing test files to understand patterns

Choose test types needed:

  • Unit tests: Individual functions, pure logic, utilities
  • Integration tests: Multiple components working together, API endpoints
  • Component tests: UI components, user interactions
  • E2E tests: Complete user flows, critical paths
  • Contract tests: API request/response validation
  • Performance tests: Load, stress, benchmark testing

2. Understand Existing Patterns

Investigate current test approach:

  • Test framework (Jest, Vitest, Pytest, etc.)
  • Mocking patterns and utilities
  • Test data fixtures and setup/teardown
  • Assertion styles

Use code-finder agents if unfamiliar with test structure.

3. Map Tests to Requirements

Convert 3-5 acceptance criteria to specific test cases across test types:

Example mapping:

## User Story: US-101 User Login

### Test Cases
1. **Unit: Authentication service**
   - validateCredentials() returns true for valid email/password
   - validateCredentials() returns false for invalid password
   - checkAccountStatus() detects locked accounts

2. **Integration: Login endpoint**
   - POST /api/login with valid creds returns 200 + token
   - POST /api/login with invalid creds returns 401 + error
   - POST /api/login with locked account returns 403

3. **Component: Login form**
   - Submitting form calls login API
   - Error message displays on 401 response
   - Success redirects to /dashboard

4. **E2E: Complete login flow**
   - User enters credentials → submits → sees dashboard
   - User enters wrong password → sees error → retries successfully

4. Write Tests

Unit Test Structure:

describe('AuthService', () => {
  describe('validateCredentials', () => {
    it('returns true for valid email and password', async () => {
      const result = await authService.validateCredentials(
        'user@example.com',
        'ValidPass123'
      );
      expect(result).toBe(true);
    });

    it('returns false for invalid password', async () => {
      const result = await authService.validateCredentials(
        'user@example.com',
        'WrongPassword'
      );
      expect(result).toBe(false);
    });
  });
});

Integration Test Structure:

describe('POST /api/auth/login', () => {
  beforeEach(async () => {
    await resetTestDatabase();
    await createTestUser({
      email: 'test@example.com',
      password: 'Test123!'
    });
  });

  it('returns 200 and token for valid credentials', async () => {
    const response = await request(app)
      .post('/api/auth/login')
      .send({ email: 'test@example.com', password: 'Test123!' });

    expect(response.status).toBe(200);
    expect(response.body).toHaveProperty('token');
    expect(response.body.token).toMatch(/^eyJ/); // JWT format
  });

  it('returns 401 for invalid password', async () => {
    const response = await request(app)
      .post('/api/auth/login')
      .send({ email: 'test@example.com', password: 'WrongPassword' });

    expect(response.status).toBe(401);
    expect(response.body.error).toBe('Invalid credentials');
  });
});

Component Test Structure:

describe('LoginForm', () => {
  it('submits form with valid data', async () => {
    const mockLogin = jest.fn().mockResolvedValue({ success: true });
    render(<LoginForm onLogin={mockLogin} />);

    await userEvent.type(screen.getByLabelText(/email/i), 'user@example.com');
    await userEvent.type(screen.getByLabelText(/password/i), 'Password123');
    await userEvent.click(screen.getByRole('button', { name: /log in/i }));

    expect(mockLogin).toHaveBeenCalledWith({
      email: 'user@example.com',
      password: 'Password123'
    });
  });

  it('displays error message on API failure', async () => {
    const mockLogin = jest.fn().mockRejectedValue(new Error('Invalid credentials'));
    render(<LoginForm onLogin={mockLogin} />);

    await userEvent.type(screen.getByLabelText(/email/i), 'user@example.com');
    await userEvent.type(screen.getByLabelText(/password/i), 'wrong');
    await userEvent.click(screen.getByRole('button', { name: /log in/i }));

    expect(await screen.findByText(/invalid credentials/i)).toBeInTheDocument();
  });
});

E2E Test Structure:

test('user can log in successfully', async ({ page }) => {
  await page.goto('/login');

  await page.fill('[name="email"]', 'test@example.com');
  await page.fill('[name="password"]', 'Test123!');
  await page.click('button:has-text("Log In")');

  await page.waitForURL('/dashboard');
  expect(page.url()).toContain('/dashboard');
});

5. Edge Cases & Error Scenarios

Include boundary conditions and error paths:

describe('Edge cases', () => {
  it('handles empty email gracefully', async () => {
    await expect(
      authService.validateCredentials('', 'password')
    ).rejects.toThrow('Email is required');
  });

  it('handles extremely long password', async () => {
    const longPassword = 'a'.repeat(10000);
    await expect(
      authService.validateCredentials('user@example.com', longPassword)
    ).rejects.toThrow('Password too long');
  });

  it('handles network timeout', async () => {
    jest.spyOn(global, 'fetch').mockImplementation(
      () => new Promise((resolve) => setTimeout(resolve, 10000))
    );

    await expect(
      authService.login('user@example.com', 'pass')
    ).rejects.toThrow('Request timeout');
  });
});

Edge cases to always include:

  • Empty/null inputs
  • Minimum/maximum values
  • Invalid formats
  • Network failures
  • API errors (4xx, 5xx)
  • Timeout conditions
  • Concurrent operations

6. Test Data & Fixtures

Create reusable test fixtures:

// tests/fixtures/users.ts
export const validUser = {
  email: 'test@example.com',
  password: 'Test123!',
  name: 'Test User'
};

export const invalidUsers = {
  noEmail: { password: 'Test123!' },
  noPassword: { email: 'test@example.com' },
  invalidEmail: { email: 'not-an-email', password: 'Test123!' },
  weakPassword: { email: 'test@example.com', password: '123' }
};

// Use in tests
import { validUser, invalidUsers } from './fixtures/users';

it('validates user data', () => {
  expect(validate(validUser)).toBe(true);
  expect(validate(invalidUsers.noEmail)).toBe(false);
});

7. Parallel Test Implementation

When tests are independent (different modules, different test types), spawn parallel agents:

Pattern 1: Layer-based

  • Agent 1: Unit tests for services/utilities
  • Agent 2: Integration tests for API endpoints
  • Agent 3: Component tests for UI
  • Agent 4: E2E tests for critical flows

Pattern 2: Feature-based

  • Agent 1: All tests for Feature A
  • Agent 2: All tests for Feature B
  • Agent 3: All tests for Feature C

Pattern 3: Type-based

  • Agent 1: All unit tests
  • Agent 2: All integration tests
  • Agent 3: All E2E tests

8. Run & Verify Tests

Execute test suite:

# Unit tests
npm test -- --coverage

# Integration tests
npm run test:integration

# E2E tests
npm run test:e2e

# All tests
npm run test:all

Verify coverage:

  • Aim for >80% code coverage
  • 100% coverage of critical paths
  • All acceptance criteria have tests
  • All error scenarios tested

Quality Checklist

Coverage:

  • All acceptance criteria from user stories tested
  • Happy path covered
  • Edge cases included
  • Error scenarios tested
  • Boundary conditions validated

Structure:

  • Tests follow existing patterns
  • Clear test descriptions
  • Proper setup/teardown
  • No flaky tests (consistent results)
  • Tests are isolated (no interdependencies)

Data:

  • Test fixtures reusable
  • Database properly seeded/reset
  • Mocks used appropriately
  • No hardcoded test data in production

Integration:

  • Tests run in CI/CD
  • Coverage thresholds enforced
  • Fast feedback (quick tests)
  • Clear failure messages
Weekly Installs
0
First Seen
Jan 1, 1970