---
description: Fullstack Testing
alwaysApply: false
---

# Fullstack Testing

Testing strategies for full-stack applications.

## Testing Pyramid

- **E2E** — critical user journeys (fewest, slowest)
- **Integration** — API + DB, components + API via MSW
- **Unit** — pure functions, isolated logic (most, fastest)

## E2E Tests

```ts
test('user can sign up', async ({ page }) => {
  await page.goto('/signup');
  await page.fill('[name="email"]', 'test@example.com');
  await page.fill('[name="password"]', 'SecurePass123!');
  await page.click('button[type="submit"]');
  await expect(page).toHaveURL('/dashboard');
});
```

Seed state via the API, not the UI — keeps E2E tests fast.

## Integration Tests

```ts
it('creates user and returns in list', async () => {
  await request(app).post('/api/users')
    .send({ email: 'new@example.com', name: 'New' }).expect(201);
  const list = await request(app).get('/api/users');
  expect(list.body.data).toContainEqual(
    expect.objectContaining({ email: 'new@example.com' })
  );
});
```

### Component + API (MSW)

Mock API responses with MSW's `setupServer` — call `server.listen()` in `beforeAll`, `server.close()` in `afterAll`. Assert rendered output after data loads with `waitFor`.

## Contract Tests

Validate responses against shared Zod schemas:

```ts
it('GET /users returns valid responses', async () => {
  const res = await request(app).get('/api/users');
  for (const user of res.body.data)
    expect(UserResponseSchema.safeParse(user).success).toBe(true);
});
```

## Test Data

```ts
import { faker } from '@faker-js/faker';
export const userInput = (overrides?) => ({
  email: faker.internet.email(), name: faker.person.fullName(), ...overrides,
});
```

## Best Practices

- Test at the right level — don't E2E-test what a unit test covers
- Always test error scenarios (API failures, validation, empty states)
- Isolate tests — clean DB state between integration tests
