Skip to content

NOTE

This document provides implementation details and migration steps. For authoritative testing conventions (directory layout, naming, test types), see Testing Conventions.

Objective

Implement comprehensive automated testing across TendSocial monorepo using approved testing tools. Migrate any existing tests from other frameworks to the standard stack.

Monorepo Structure

tendsocial/
├── apps/
│   ├── api/          # Backend API (Node.js/Express)
│   ├── app/          # Frontend Application (React)
│   └── web/          # Marketing Site (React)
├── packages/         # Shared packages (if any)
└── package.json      # Root package.json

Approved Testing Stack

Core Testing Tools (ONLY use these)

  1. Vitest - Test runner for unit and integration tests (all workspaces)
  2. React Testing Library - React component testing (app + web)
  3. Playwright - End-to-end browser testing (root level)
  4. Supertest - API endpoint testing (api only)
  5. MSW (Mock Service Worker) - API mocking (app + web)

Implementation Requirements

1. Install Dependencies

Root Level (for E2E tests) - DONE!

bash
# From monorepo root
pnpm add -D -w playwright @playwright/test

# Initialize Playwright browsers
pnpm exec playwright install

apps/backend (Backend)

bash
# From monorepo root
pnpm add -D --filter=api vitest @vitest/ui
pnpm add -D --filter=api supertest @types/supertest

apps/frontend (Frontend Application) - DONE!

bash
# From monorepo root
pnpm add -D --filter=app vitest @vitest/ui
pnpm add -D --filter=app @testing-library/react @testing-library/dom @testing-library/user-event @testing-library/jest-dom
pnpm add -D --filter=app msw

apps/marketing (Marketing Site) - DONE!

bash
# From monorepo root
pnpm add -D --filter=web vitest @vitest/ui
pnpm add -D --filter=web @testing-library/react @testing-library/dom @testing-library/user-event @testing-library/jest-dom
pnpm add -D --filter=web msw

2. Configuration Files to Create

Root Level - playwright.config.ts

typescript
import { defineConfig, devices } from '@playwright/test'

export default defineConfig({
  testDir: './test/e2e',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  reporter: 'html',
  use: {
    baseURL: 'http://localhost:3000', // Adjust based on your app port
    trace: 'on-first-retry',
  },
  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },
    {
      name: 'firefox',
      use: { ...devices['Desktop Firefox'] },
    },
    {
      name: 'webkit',
      use: { ...devices['Desktop Safari'] },
    },
  ],
  webServer: [
    {
      command: 'pnpm --filter=api dev',
      url: 'http://localhost:4000',
      reuseExistingServer: !process.env.CI,
    },
    {
      command: 'pnpm --filter=app dev',
      url: 'http://localhost:3000',
      reuseExistingServer: !process.env.CI,
    },
  ],
})

apps/backend/vitest.config.ts

typescript
import { defineConfig } from 'vitest/config'
import path from 'path'

export default defineConfig({
  test: {
    globals: true,
    environment: 'node',
    setupFiles: ['./src/test/setup.ts'],
    coverage: {
      provider: 'v8',
      reporter: ['text', 'json', 'html'],
      exclude: ['**/node_modules/**', '**/dist/**'],
    },
  },
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
    },
  },
})

apps/backend/src/test/setup.ts

typescript
import { beforeAll, afterAll, afterEach } from 'vitest'

// Global test setup for API tests
beforeAll(() => {
  // Setup test database, etc.
})

afterEach(() => {
  // Clean up after each test
})

afterAll(() => {
  // Cleanup connections, etc.
})

apps/frontend/vitest.config.ts

typescript
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
import path from 'path'

export default defineConfig({
  plugins: [react()],
  test: {
    globals: true,
    environment: 'jsdom',
    setupFiles: ['./src/test/setup.ts'],
    coverage: {
      provider: 'v8',
      reporter: ['text', 'json', 'html'],
      exclude: ['**/node_modules/**', '**/dist/**', '**/*.config.*'],
    },
  },
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
    },
  },
})

apps/frontend/src/test/setup.ts

typescript
import { expect, afterEach, beforeAll, afterAll } from 'vitest'
import { cleanup } from '@testing-library/react'
import * as matchers from '@testing-library/jest-dom/matchers'
import { server } from '../mocks/server'

expect.extend(matchers)

// Start MSW server before all tests
beforeAll(() => server.listen())

// Cleanup after each test
afterEach(() => {
  cleanup()
  server.resetHandlers()
})

// Stop MSW server after all tests
afterAll(() => server.close())

apps/frontend/src/mocks/handlers.ts

typescript
import { http, HttpResponse } from 'msw'

export const handlers = [
  // Define your API mocks here
  http.get('/api/example', () => {
    return HttpResponse.json({ message: 'mocked response' })
  }),
]

apps/frontend/src/mocks/browser.ts

typescript
import { setupWorker } from 'msw/browser'
import { handlers } from './handlers'

export const worker = setupWorker(...handlers)

apps/frontend/src/mocks/server.ts

typescript
import { setupServer } from 'msw/node'
import { handlers } from './handlers'

export const server = setupServer(...handlers)

apps/marketing/vitest.config.ts

typescript
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
import path from 'path'

export default defineConfig({
  plugins: [react()],
  test: {
    globals: true,
    environment: 'jsdom',
    setupFiles: ['./src/test/setup.ts'],
    coverage: {
      provider: 'v8',
      reporter: ['text', 'json', 'html'],
      exclude: ['**/node_modules/**', '**/dist/**', '**/*.config.*'],
    },
  },
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
    },
  },
})

apps/marketing/src/test/setup.ts

typescript
import { expect, afterEach, beforeAll, afterAll } from 'vitest'
import { cleanup } from '@testing-library/react'
import * as matchers from '@testing-library/jest-dom/matchers'
import { server } from '../mocks/server'

expect.extend(matchers)

// Start MSW server before all tests
beforeAll(() => server.listen())

// Cleanup after each test
afterEach(() => {
  cleanup()
  server.resetHandlers()
})

// Stop MSW server after all tests
afterAll(() => server.close())

apps/marketing/src/mocks/ (same structure as apps/frontend)

Create the same MSW setup files as in apps/frontend

3. Package.json Scripts

Root package.json

json
{
  "scripts": {
    "test": "pnpm -r test",
    "test:api": "pnpm --filter=api test",
    "test:app": "pnpm --filter=app test",
    "test:web": "pnpm --filter=web test",
    "test:e2e": "playwright test",
    "test:e2e:ui": "playwright test --ui",
    "test:coverage": "pnpm -r test:coverage"
  }
}

apps/backend/package.json

json
{
  "scripts": {
    "test": "vitest",
    "test:ui": "vitest --ui",
    "test:coverage": "vitest --coverage"
  }
}

apps/frontend/package.json

json
{
  "scripts": {
    "test": "vitest",
    "test:ui": "vitest --ui",
    "test:coverage": "vitest --coverage"
  }
}

apps/marketing/package.json

json
{
  "scripts": {
    "test": "vitest",
    "test:ui": "vitest --ui",
    "test:coverage": "vitest --coverage"
  }
}

4. Migration Tasks

Migrate FROM these (if found):

  • Jest → Vitest
  • Enzyme → React Testing Library
  • Cypress → Playwright
  • Selenium → Playwright
  • TestCafe → Playwright
  • react-test-renderer → React Testing Library
  • Mocha/Chai → Vitest
  • Jasmine → Vitest
  • Any custom fetch mocking → MSW

Migration Steps:

  1. Search for existing test files using deprecated tools
  2. Convert test syntax to approved tools
  3. Remove deprecated dependencies from package.json
  4. Update imports in all test files
  5. Verify all tests pass after migration

5. Test Organization Structure

tendsocial/
├── test/
│   └── e2e/                        # E2E tests (Playwright)
│       ├── auth.spec.ts
│       ├── user-flow.spec.ts
│       └── ...
├── apps/
│   ├── api/
│   │   ├── src/
│   │   │   ├── routes/
│   │   │   │   ├── users.ts
│   │   │   │   └── users.test.ts   # Vitest + Supertest
│   │   │   ├── services/
│   │   │   │   ├── auth.ts
│   │   │   │   └── auth.test.ts    # Vitest
│   │   │   └── test/
│   │   │       └── setup.ts
│   │   └── vitest.config.ts
│   ├── app/
│   │   ├── src/
│   │   │   ├── components/
│   │   │   │   ├── Button/
│   │   │   │   │   ├── Button.tsx
│   │   │   │   │   └── Button.test.tsx  # Vitest + RTL
│   │   │   ├── hooks/
│   │   │   │   ├── useAuth.ts
│   │   │   │   └── useAuth.test.ts      # Vitest
│   │   │   ├── mocks/
│   │   │   │   ├── handlers.ts
│   │   │   │   ├── browser.ts
│   │   │   │   └── server.ts
│   │   │   └── test/
│   │   │       └── setup.ts
│   │   └── vitest.config.ts
│   └── web/
│       ├── src/
│       │   ├── components/
│       │   │   ├── Hero/
│       │   │   │   ├── Hero.tsx
│       │   │   │   └── Hero.test.tsx    # Vitest + RTL
│       │   ├── mocks/
│       │   │   ├── handlers.ts
│       │   │   ├── browser.ts
│       │   │   └── server.ts
│       │   └── test/
│       │       └── setup.ts
│       └── vitest.config.ts
└── playwright.config.ts

6. Test Writing Guidelines

Component Tests (Vitest + React Testing Library):

typescript
import { render, screen } from '@testing-library/react'
import { describe, it, expect } from 'vitest'
import { Button } from './Button'

describe('Button', () => {
  it('renders with correct text', () => {
    render(<Button>Click me</Button>)
    expect(screen.getByText('Click me')).toBeInTheDocument()
  })
})

API Tests (Vitest + Supertest):

typescript
import { describe, it, expect } from 'vitest'
import request from 'supertest'
import { app } from '../app'

describe('GET /api/users', () => {
  it('returns users list', async () => {
    const response = await request(app)
      .get('/api/users')
      .expect(200)
    
    expect(response.body).toHaveProperty('users')
  })
})

E2E Tests (Playwright):

typescript
import { test, expect } from '@playwright/test'

test('user can log in', async ({ page }) => {
  await page.goto('/')
  await page.click('text=Login')
  await page.fill('[name="email"]', 'test@example.com')
  await page.fill('[name="password"]', 'password')
  await page.click('button[type="submit"]')
  
  await expect(page).toHaveURL('/dashboard')
})

With MSW Mocking:

typescript
import { render, screen, waitFor } from '@testing-library/react'
import { describe, it, expect, beforeAll, afterAll, afterEach } from 'vitest'
import { server } from '../mocks/server'
import { UserProfile } from './UserProfile'

beforeAll(() => server.listen())
afterEach(() => server.resetHandlers())
afterAll(() => server.close())

describe('UserProfile', () => {
  it('loads and displays user data', async () => {
    render(<UserProfile userId="123" />)
    
    await waitFor(() => {
      expect(screen.getByText('John Doe')).toBeInTheDocument()
    })
  })
})

Action Items for AI Agent

Phase 1: Setup & Configuration

  1. Install dependencies in correct workspaces

    • Root: Playwright only
    • apps/backend: Vitest, Supertest
    • apps/frontend: Vitest, React Testing Library, MSW
    • apps/marketing: Vitest, React Testing Library, MSW
  2. Create configuration files

    • Root: playwright.config.ts
    • apps/backend: vitest.config.ts + test setup
    • apps/frontend: vitest.config.ts + test setup + MSW setup
    • apps/marketing: vitest.config.ts + test setup + MSW setup
  3. Add test scripts to all package.json files

    • Root: orchestration scripts
    • Each workspace: local test scripts

Phase 2: Migration

  1. Audit existing tests in each workspace

    • apps/backend: Check for Jest, Mocha, or other test runners
    • apps/frontend: Check for Jest, Enzyme, Cypress
    • apps/marketing: Check for Jest, Enzyme, Cypress
  2. Migrate existing tests

    • Convert test syntax to approved tools
    • Update imports in all test files
    • Remove deprecated dependencies from package.json files

Phase 3: Implementation

  1. Create test folder structure as specified above

  2. Write missing tests:

    • apps/backend: All API routes and services
    • apps/frontend: All React components, hooks, and utilities
    • apps/marketing: All React components (especially marketing pages)
    • test/e2e/: Critical user flows across the full application
  3. Set up MSW handlers (apps/frontend and apps/marketing only)

    • Define handlers for all external API calls
    • Share common handlers if needed
  4. Update CI/CD pipelines

    • Ensure all workspace tests run: pnpm test
    • Ensure E2E tests run: pnpm test:e2e
    • Generate and upload coverage reports
  5. Generate coverage reports - aim for 80%+ coverage per workspace

Validation Checklist

Workspace-Specific Validation

apps/backend

  • [ ] No deprecated testing libraries in package.json
  • [ ] Vitest and Supertest installed and configured
  • [ ] vitest.config.ts created and working
  • [ ] All API routes have tests
  • [ ] All services have tests
  • [ ] Tests passing: pnpm --filter=api test

apps/frontend

  • [ ] No deprecated testing libraries in package.json
  • [ ] Vitest, React Testing Library, and MSW installed
  • [ ] vitest.config.ts created and working
  • [ ] MSW handlers defined
  • [ ] All components have tests
  • [ ] All hooks have tests
  • [ ] Tests passing: pnpm --filter=app test

apps/marketing

  • [ ] No deprecated testing libraries in package.json
  • [ ] Vitest, React Testing Library, and MSW installed
  • [ ] vitest.config.ts created and working
  • [ ] MSW handlers defined (if needed)
  • [ ] Marketing components have tests
  • [ ] Tests passing: pnpm --filter=web test

Root (E2E)

  • [ ] Playwright installed at root level
  • [ ] playwright.config.ts created and working
  • [ ] Critical user flows covered
  • [ ] E2E tests passing: pnpm test:e2e

Overall Validation

  • [ ] All tests pass: pnpm test
  • [ ] Coverage reports generating: pnpm test:coverage
  • [ ] E2E tests can run locally and in CI
  • [ ] CI/CD pipeline updated with monorepo test commands

Notes

General

  • Use Vitest's built-in coverage (no need for separate istanbul/nyc)
  • Playwright includes visual regression testing capabilities
  • MSW works in both browser and Node.js environments
  • React Testing Library encourages testing user behavior, not implementation
  • All tools are free for commercial use and actively maintained as of November 2025

Monorepo-Specific

  • Use pnpm --filter=<workspace> to run commands in specific workspaces
  • Use pnpm -r to run commands recursively in all workspaces
  • Playwright runs at root level and can test across all apps
  • MSW is only needed in frontend workspaces (app, web)
  • Supertest is only needed in backend workspace (api)
  • Each workspace maintains its own test configuration
  • Shared test utilities can be placed in a packages/test-utils workspace if needed

Running Tests

bash
# Run all tests in all workspaces
pnpm test

# Run tests in specific workspace
pnpm --filter=api test
pnpm --filter=app test
pnpm --filter=web test

# Run E2E tests
pnpm test:e2e

# Run with UI
pnpm --filter=app test:ui
pnpm test:e2e:ui

# Generate coverage
pnpm test:coverage

Execute this migration systematically. Start with configuration, then migrate existing tests, then write new tests for uncovered code.

TendSocial Documentation