Skip to main content
What you’ll get out of this: Master comprehensive testing strategies for DeelRx CRM, including unit testing, integration testing, E2E testing, and performance testing with practical examples and best practices.

Testing Strategy

Testing Pyramid

Unit Tests

  • Fast execution (< 1ms)
  • Isolated components
  • Business logic testing
  • High coverage (80%+)

Integration Tests

  • Medium execution (1-100ms)
  • API endpoint testing
  • Database integration
  • External service mocking

E2E Tests

  • Slower execution (100ms+)
  • User workflow testing
  • Cross-browser testing
  • Critical path coverage

Testing Tools

Core Testing Framework

  • Fast unit testing framework
  • Jest-compatible API
  • TypeScript support
  • Watch mode and coverage
  • Component testing utilities
  • User-centric testing approach
  • Accessibility testing
  • Custom render functions
  • E2E testing framework
  • Cross-browser testing
  • Mobile device testing
  • Visual regression testing

Unit Testing

Component Testing

// tests/components/CustomerCard.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { CustomerCard } from '@/components/CustomerCard';
import { Customer } from '@/types/customer';

const mockCustomer: Customer = {
  id: '1',
  firstName: 'John',
  lastName: 'Doe',
  email: '[email protected]',
  phone: '+1234567890',
  createdAt: new Date('2024-01-01')
};

describe('CustomerCard', () => {
  it('renders customer information correctly', () => {
    render(<CustomerCard customer={mockCustomer} />);
    
    expect(screen.getByText('John Doe')).toBeInTheDocument();
    expect(screen.getByText('[email protected]')).toBeInTheDocument();
    expect(screen.getByText('+1234567890')).toBeInTheDocument();
  });

  it('calls onEdit when edit button is clicked', () => {
    const onEdit = vi.fn();
    render(<CustomerCard customer={mockCustomer} onEdit={onEdit} />);
    
    fireEvent.click(screen.getByRole('button', { name: /edit/i }));
    expect(onEdit).toHaveBeenCalledWith(mockCustomer);
  });

  it('shows loading state when customer is being updated', () => {
    render(<CustomerCard customer={mockCustomer} isLoading={true} />);
    
    expect(screen.getByTestId('loading-spinner')).toBeInTheDocument();
  });
});

Business Logic Testing

// tests/utils/calculations.test.ts
import { calculateTotal, calculateTax, formatCurrency } from '@/utils/calculations';

describe('Business Logic Calculations', () => {
  describe('calculateTotal', () => {
    it('calculates total with tax correctly', () => {
      const items = [
        { price: 100, quantity: 2 },
        { price: 50, quantity: 1 }
      ];
      const taxRate = 0.1;
      
      const result = calculateTotal(items, taxRate);
      expect(result).toBe(275); // (200 + 50) * 1.1
    });

    it('handles empty items array', () => {
      const result = calculateTotal([], 0.1);
      expect(result).toBe(0);
    });

    it('handles zero tax rate', () => {
      const items = [{ price: 100, quantity: 1 }];
      const result = calculateTotal(items, 0);
      expect(result).toBe(100);
    });
  });

  describe('formatCurrency', () => {
    it('formats USD correctly', () => {
      expect(formatCurrency(1234.56, 'USD')).toBe('$1,234.56');
    });

    it('formats EUR correctly', () => {
      expect(formatCurrency(1234.56, 'EUR')).toBe('€1,234.56');
    });

    it('handles zero amount', () => {
      expect(formatCurrency(0, 'USD')).toBe('$0.00');
    });
  });
});

API Route Testing

// tests/api/customers.test.ts
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { GET, POST } from '@/app/api/customers/route';
import { NextRequest } from 'next/server';
import { database } from '@/lib/database';

// Mock database
vi.mock('@/lib/database', () => ({
  database: {
    customer: {
      findMany: vi.fn(),
      create: vi.fn(),
      findUnique: vi.fn()
    }
  }
}));

describe('/api/customers', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  describe('GET', () => {
    it('returns customers list', async () => {
      const mockCustomers = [
        { id: '1', firstName: 'John', lastName: 'Doe', email: '[email protected]' }
      ];
      
      database.customer.findMany.mockResolvedValue(mockCustomers);
      
      const request = new NextRequest('http://localhost:3000/api/customers');
      const response = await GET(request);
      const data = await response.json();
      
      expect(response.status).toBe(200);
      expect(data.data).toEqual(mockCustomers);
    });

    it('handles search parameters', async () => {
      const request = new NextRequest('http://localhost:3000/api/customers?search=john');
      
      await GET(request);
      
      expect(database.customer.findMany).toHaveBeenCalledWith(
        expect.objectContaining({
          where: expect.objectContaining({
            OR: expect.arrayContaining([
              { firstName: { contains: 'john', mode: 'insensitive' } }
            ])
          })
        })
      );
    });
  });

  describe('POST', () => {
    it('creates a new customer', async () => {
      const customerData = {
        firstName: 'Jane',
        lastName: 'Smith',
        email: '[email protected]'
      };
      
      const mockCustomer = { id: '2', ...customerData };
      database.customer.create.mockResolvedValue(mockCustomer);
      database.customer.findUnique.mockResolvedValue(null);
      
      const request = new NextRequest('http://localhost:3000/api/customers', {
        method: 'POST',
        body: JSON.stringify(customerData),
        headers: { 'Content-Type': 'application/json' }
      });
      
      const response = await POST(request);
      const data = await response.json();
      
      expect(response.status).toBe(201);
      expect(data.data).toEqual(mockCustomer);
    });

    it('returns error for duplicate email', async () => {
      const customerData = {
        firstName: 'Jane',
        lastName: 'Smith',
        email: '[email protected]'
      };
      
      database.customer.findUnique.mockResolvedValue({ id: '1' });
      
      const request = new NextRequest('http://localhost:3000/api/customers', {
        method: 'POST',
        body: JSON.stringify(customerData),
        headers: { 'Content-Type': 'application/json' }
      });
      
      const response = await POST(request);
      const data = await response.json();
      
      expect(response.status).toBe(409);
      expect(data.error).toContain('already exists');
    });
  });
});

Integration Testing

Database Integration

// tests/integration/database.test.ts
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { PrismaClient } from '@prisma/client';
import { seedDatabase, cleanupDatabase } from '@/tests/helpers/database';

const prisma = new PrismaClient();

describe('Database Integration', () => {
  beforeAll(async () => {
    await seedDatabase(prisma);
  });

  afterAll(async () => {
    await cleanupDatabase(prisma);
    await prisma.$disconnect();
  });

  it('creates and retrieves customer', async () => {
    const customerData = {
      firstName: 'Integration',
      lastName: 'Test',
      email: '[email protected]',
      teamId: 'test-team'
    };

    const customer = await prisma.customer.create({
      data: customerData
    });

    expect(customer.id).toBeDefined();
    expect(customer.firstName).toBe(customerData.firstName);

    const retrieved = await prisma.customer.findUnique({
      where: { id: customer.id }
    });

    expect(retrieved).toEqual(customer);
  });

  it('handles customer relationships', async () => {
    const customer = await prisma.customer.create({
      data: {
        firstName: 'Customer',
        lastName: 'WithOrders',
        email: '[email protected]',
        teamId: 'test-team'
      }
    });

    const order = await prisma.order.create({
      data: {
        customerId: customer.id,
        total: 100.00,
        status: 'PENDING',
        teamId: 'test-team'
      }
    });

    const customerWithOrders = await prisma.customer.findUnique({
      where: { id: customer.id },
      include: { orders: true }
    });

    expect(customerWithOrders?.orders).toHaveLength(1);
    expect(customerWithOrders?.orders[0].id).toBe(order.id);
  });
});

API Integration

// tests/integration/api.test.ts
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { createTestServer } from '@/tests/helpers/server';
import { seedTestData } from '@/tests/helpers/seed';

describe('API Integration', () => {
  let server: any;

  beforeAll(async () => {
    server = await createTestServer();
    await seedTestData();
  });

  afterAll(async () => {
    await server.close();
  });

  it('completes customer creation workflow', async () => {
    // Create customer
    const createResponse = await server.post('/api/customers')
      .send({
        firstName: 'API',
        lastName: 'Test',
        email: '[email protected]'
      })
      .expect(201);

    const customerId = createResponse.body.data.id;

    // Create order for customer
    const orderResponse = await server.post('/api/orders')
      .send({
        customerId,
        items: [
          { productId: '1', quantity: 2, price: 50.00 }
        ],
        total: 100.00
      })
      .expect(201);

    // Verify customer has order
    const customerResponse = await server.get(`/api/customers/${customerId}`)
      .expect(200);

    expect(customerResponse.body.data.orders).toHaveLength(1);
    expect(customerResponse.body.data.orders[0].id).toBe(orderResponse.body.data.id);
  });

  it('handles payment processing workflow', async () => {
    const customer = await server.post('/api/customers')
      .send({
        firstName: 'Payment',
        lastName: 'Test',
        email: '[email protected]'
      })
      .expect(201);

    const order = await server.post('/api/orders')
      .send({
        customerId: customer.body.data.id,
        items: [{ productId: '1', quantity: 1, price: 100.00 }],
        total: 100.00
      })
      .expect(201);

    const payment = await server.post('/api/payments')
      .send({
        orderId: order.body.data.id,
        amount: 100.00,
        method: 'CARD',
        status: 'COMPLETED'
      })
      .expect(201);

    expect(payment.body.data.status).toBe('COMPLETED');
  });
});

End-to-End Testing

User Workflow Testing

// tests/e2e/customer-workflow.spec.ts
import { test, expect } from '@playwright/test';

test.describe('Customer Management Workflow', () => {
  test('complete customer lifecycle', async ({ page }) => {
    // Navigate to customers page
    await page.goto('/customers');
    
    // Add new customer
    await page.click('[data-testid="add-customer-button"]');
    await page.fill('[data-testid="first-name-input"]', 'E2E');
    await page.fill('[data-testid="last-name-input"]', 'Test');
    await page.fill('[data-testid="email-input"]', '[email protected]');
    await page.click('[data-testid="save-customer-button"]');
    
    // Verify customer appears in list
    await expect(page.locator('[data-testid="customer-list"]')).toContainText('E2E Test');
    
    // Click on customer to view details
    await page.click('[data-testid="customer-row"]:has-text("E2E Test")');
    
    // Verify customer details page
    await expect(page.locator('[data-testid="customer-name"]')).toContainText('E2E Test');
    await expect(page.locator('[data-testid="customer-email"]')).toContainText('[email protected]');
    
    // Add order for customer
    await page.click('[data-testid="add-order-button"]');
    await page.fill('[data-testid="order-total"]', '150.00');
    await page.selectOption('[data-testid="order-status"]', 'PENDING');
    await page.click('[data-testid="save-order-button"]');
    
    // Verify order appears in customer's orders
    await expect(page.locator('[data-testid="orders-list"]')).toContainText('$150.00');
    
    // Edit customer information
    await page.click('[data-testid="edit-customer-button"]');
    await page.fill('[data-testid="phone-input"]', '+1234567890');
    await page.click('[data-testid="save-changes-button"]');
    
    // Verify phone number was updated
    await expect(page.locator('[data-testid="customer-phone"]')).toContainText('+1234567890');
  });

  test('search and filter customers', async ({ page }) => {
    await page.goto('/customers');
    
    // Search for specific customer
    await page.fill('[data-testid="search-input"]', 'E2E');
    await page.press('[data-testid="search-input"]', 'Enter');
    
    // Verify search results
    await expect(page.locator('[data-testid="customer-list"]')).toContainText('E2E Test');
    
    // Filter by status
    await page.selectOption('[data-testid="status-filter"]', 'ACTIVE');
    
    // Verify filtered results
    const customerRows = page.locator('[data-testid="customer-row"]');
    await expect(customerRows).toHaveCount(1);
  });
});

Cross-Browser Testing

// tests/e2e/cross-browser.spec.ts
import { test, expect, devices } from '@playwright/test';

test.describe('Cross-Browser Compatibility', () => {
  test('works on desktop Chrome', async ({ page }) => {
    await page.goto('/');
    await expect(page.locator('[data-testid="main-navigation"]')).toBeVisible();
  });

  test('works on mobile Safari', async ({ page }) => {
    await page.goto('/');
    await expect(page.locator('[data-testid="mobile-menu"]')).toBeVisible();
  });

  test('responsive design works', async ({ page }) => {
    // Test desktop view
    await page.setViewportSize({ width: 1920, height: 1080 });
    await page.goto('/customers');
    await expect(page.locator('[data-testid="desktop-layout"]')).toBeVisible();
    
    // Test mobile view
    await page.setViewportSize({ width: 375, height: 667 });
    await page.goto('/customers');
    await expect(page.locator('[data-testid="mobile-layout"]')).toBeVisible();
  });
});

Performance Testing

Load Testing

// tests/performance/load.test.ts
import { test, expect } from '@playwright/test';

test.describe('Performance Testing', () => {
  test('handles concurrent customer creation', async ({ browser }) => {
    const contexts = await Promise.all(
      Array.from({ length: 10 }, () => browser.newContext())
    );
    
    const pages = await Promise.all(
      contexts.map(context => context.newPage())
    );
    
    // Navigate all pages to customers
    await Promise.all(
      pages.map(page => page.goto('/customers'))
    );
    
    // Create customers concurrently
    const customerPromises = pages.map((page, index) => 
      page.click('[data-testid="add-customer-button"]')
        .then(() => page.fill('[data-testid="first-name-input"]', `Load${index}`))
        .then(() => page.fill('[data-testid="last-name-input"]', 'Test'))
        .then(() => page.fill('[data-testid="email-input"]', `load${index}@test.com`))
        .then(() => page.click('[data-testid="save-customer-button"]'))
    );
    
    await Promise.all(customerPromises);
    
    // Verify all customers were created
    const firstPage = pages[0];
    await firstPage.reload();
    const customerCount = await firstPage.locator('[data-testid="customer-row"]').count();
    expect(customerCount).toBeGreaterThanOrEqual(10);
  });

  test('API response times are acceptable', async ({ request }) => {
    const startTime = Date.now();
    
    const response = await request.get('/api/customers');
    
    const endTime = Date.now();
    const responseTime = endTime - startTime;
    
    expect(response.status()).toBe(200);
    expect(responseTime).toBeLessThan(1000); // Should respond within 1 second
  });
});

Test Configuration

Vitest Configuration

// vitest.config.ts
import { defineConfig } from 'vitest/config';
import { resolve } from 'path';

export default defineConfig({
  test: {
    globals: true,
    environment: 'jsdom',
    setupFiles: ['./tests/setup.ts'],
    coverage: {
      provider: 'v8',
      reporter: ['text', 'json', 'html'],
      exclude: [
        'node_modules/',
        'tests/',
        '**/*.d.ts',
        '**/*.config.*'
      ],
      thresholds: {
        global: {
          branches: 80,
          functions: 80,
          lines: 80,
          statements: 80
        }
      }
    }
  },
  resolve: {
    alias: {
      '@': resolve(__dirname, './src')
    }
  }
});

Playwright Configuration

// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './tests/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',
    trace: 'on-first-retry',
  },
  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },
    {
      name: 'firefox',
      use: { ...devices['Desktop Firefox'] },
    },
    {
      name: 'webkit',
      use: { ...devices['Desktop Safari'] },
    },
    {
      name: 'Mobile Chrome',
      use: { ...devices['Pixel 5'] },
    },
    {
      name: 'Mobile Safari',
      use: { ...devices['iPhone 12'] },
    },
  ],
  webServer: {
    command: 'pnpm dev',
    url: 'http://localhost:3000',
    reuseExistingServer: !process.env.CI,
  },
});

Test Utilities and Helpers

Database Helpers

// tests/helpers/database.ts
import { PrismaClient } from '@prisma/client';

export async function seedTestData(prisma: PrismaClient) {
  await prisma.customer.createMany({
    data: [
      {
        id: 'test-customer-1',
        firstName: 'Test',
        lastName: 'Customer',
        email: '[email protected]',
        teamId: 'test-team'
      }
    ]
  });
}

export async function cleanupTestData(prisma: PrismaClient) {
  await prisma.customer.deleteMany({
    where: { teamId: 'test-team' }
  });
}

Component Testing Helpers

// tests/helpers/render.tsx
import { render, RenderOptions } from '@testing-library/react';
import { ReactElement } from 'react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { BrowserRouter } from 'react-router-dom';

const AllTheProviders = ({ children }: { children: React.ReactNode }) => {
  const queryClient = new QueryClient({
    defaultOptions: {
      queries: {
        retry: false,
      },
    },
  });

  return (
    <QueryClientProvider client={queryClient}>
      <BrowserRouter>
        {children}
      </BrowserRouter>
    </QueryClientProvider>
  );
};

const customRender = (
  ui: ReactElement,
  options?: Omit<RenderOptions, 'wrapper'>,
) => render(ui, { wrapper: AllTheProviders, ...options });

export * from '@testing-library/react';
export { customRender as render };

Best Practices

Testing Guidelines

  • Write tests that test behavior, not implementation
  • Use descriptive test names that explain what is being tested
  • Keep tests simple and focused on one thing
  • Use proper test data setup and teardown
  • Mock external dependencies appropriately

Coverage Goals

  • Aim for 80%+ code coverage
  • Focus on critical business logic
  • Test error conditions and edge cases
  • Include accessibility testing
  • Test both happy path and error scenarios

Continuous Integration

GitHub Actions

# .github/workflows/test.yml
name: Tests

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '20'
          cache: 'pnpm'
      
      - name: Install dependencies
        run: pnpm install
      
      - name: Run unit tests
        run: pnpm test:unit
      
      - name: Run integration tests
        run: pnpm test:integration
      
      - name: Run E2E tests
        run: pnpm test:e2e
      
      - name: Upload coverage
        uses: codecov/codecov-action@v3
        with:
          file: ./coverage/lcov.info

Next Steps