Skip to main content
GatePass uses Vitest for frontend testing and Jest for backend testing. This guide covers how to run tests, write new tests, and follow testing best practices.

Testing Stack

Vitest

Fast unit testing for frontend codeCompatible with Jest API

Jest

Comprehensive testing for backendWith Supertest for API testing

React Testing Library

Component testing utilitiesUser-centric testing approach

Supertest

HTTP assertion libraryFor testing Express APIs

Running Tests

Frontend Tests (Vitest)

Run frontend tests using Vitest:
npm run test

Backend Tests (Jest)

Run backend tests from the server package:
cd src/packages/server
npm test

Test Structure

Frontend Test Example

Location: src/utils/ticketing/events.test.ts:1-44
import { describe, it, expect } from 'vitest';
import { mapLocalEventToAggregated } from './events';

describe('Event Mapping Utility', () => {
  it('should correctly map a local event to an aggregated event', () => {
    const localEvent = {
      id: 'event-123',
      title: 'Test Event',
      description: 'Test Description',
      date: '2024-12-01',
      venue: 'Test Venue',
      category: 'Music',
      status: 'live',
      ticketTiers: [
        { id: 'tier-1', name: 'VIP', price: 100 },
        { id: 'tier-2', name: 'Regular', price: 50 }
      ]
    };

    const result = mapLocalEventToAggregated(localEvent);

    expect(result.id).toBe('event-123');
    expect(result.title).toBe('Test Event');
    expect(result.price).toBe(50); // Min price
    expect(result.tiers).toHaveLength(2);
  });

  it('should handle missing fields with defaults', () => {
    const localEvent = {
      id: 'event-456',
      title: 'Empty Event'
    };

    const result = mapLocalEventToAggregated(localEvent);

    expect(result.category).toBe('Technology');
    expect(result.status).toBe('PUBLISHED');
    expect(result.tiers).toEqual([]);
  });
});

Backend Test Example

Location: src/services/authService.test.ts:1-54
import { describe, it, expect, vi, beforeEach } from 'vitest';
import axios from 'axios';
import { loginUser, registerUser, logoutUser } from './authService';
import * as sessionUtils from '../utils/session';

vi.mock('axios');
vi.mock('../utils/session');

describe('Auth Service', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  describe('loginUser', () => {
    it('should return success and set session on successful login', async () => {
      const mockUser = { 
        id: '1', 
        email: '[email protected]', 
        role: 'ORGANIZER' 
      };
      const mockResponse = { 
        data: { token: 'valid-token', user: mockUser } 
      };
      
      (axios.post as any).mockResolvedValue(mockResponse);

      const result = await loginUser('[email protected]', 'password123');

      expect(result.success).toBe(true);
      expect(result.token).toBe('valid-token');
      expect(sessionUtils.setSession).toHaveBeenCalledWith({
        token: 'valid-token',
        role: 'organizer',
        email: '[email protected]'
      });
    });

    it('should use fallback on network error', async () => {
      (axios.post as any).mockRejectedValue(new Error('Network Error'));

      const result = await loginUser('[email protected]', 'password123');

      expect(result.success).toBe(true);
      expect(result.token).toBe('dummy-token');
    });
  });
});

Test Organization

Frontend Tests

src/
├── services/
│   ├── authService.ts
│   └── authService.test.ts
├── utils/
│   ├── session.ts
│   ├── session.test.ts
│   ├── security.test.ts
│   └── ticketing/
│       ├── events.ts
│       └── events.test.ts
└── components/
    ├── EventCard.tsx
    └── EventCard.test.tsx

Backend Tests

src/packages/server/
├── src/
│   ├── controllers/
│   │   ├── eventController.ts
│   │   └── eventController.test.ts
│   ├── services/
│   │   ├── ticketService.ts
│   │   └── ticketService.test.ts
│   └── middleware/
│       ├── auth.ts
│       └── auth.test.ts
└── __tests__/
    ├── integration/
    │   ├── events.test.ts
    │   └── orders.test.ts
    └── e2e/
        └── checkout.test.ts

Writing Tests

Unit Tests

Test individual functions and components in isolation.
import { describe, it, expect } from 'vitest';
import { calculateTicketPrice } from './pricing';

describe('calculateTicketPrice', () => {
  it('should calculate price with service fee', () => {
    const basePrice = 100;
    const result = calculateTicketPrice(basePrice);
    
    expect(result.subtotal).toBe(100);
    expect(result.serviceFee).toBe(5);
    expect(result.total).toBe(105);
  });

  it('should apply discount correctly', () => {
    const result = calculateTicketPrice(100, { discount: 10 });
    
    expect(result.subtotal).toBe(90);
    expect(result.total).toBe(94.5);
  });
});

Integration Tests

Test how multiple parts of the application work together.
API Integration Test
import request from 'supertest';
import { app } from '../app';
import { PrismaClient } from '@passmint/database';

const prisma = new PrismaClient();

describe('Event API', () => {
  beforeAll(async () => {
    // Setup test database
    await prisma.$connect();
  });

  afterAll(async () => {
    // Cleanup
    await prisma.event.deleteMany();
    await prisma.$disconnect();
  });

  describe('POST /api/events', () => {
    it('should create a new event', async () => {
      const response = await request(app)
        .post('/api/events')
        .set('Authorization', `Bearer ${authToken}`)
        .send({
          title: 'Test Event',
          venue: 'Test Venue',
          eventDate: '2024-12-01',
          ticketPrice: 50,
          totalSupply: 100,
        })
        .expect(201);

      expect(response.body).toHaveProperty('id');
      expect(response.body.title).toBe('Test Event');
    });

    it('should return 400 for invalid data', async () => {
      await request(app)
        .post('/api/events')
        .set('Authorization', `Bearer ${authToken}`)
        .send({ title: '' })
        .expect(400);
    });
  });

  describe('GET /api/events/:id', () => {
    it('should return event details', async () => {
      const event = await prisma.event.create({
        data: {
          title: 'Sample Event',
          venue: 'Venue',
          eventDate: new Date('2024-12-01'),
          ticketPrice: 50,
          totalSupply: 100,
          organizerId: userId,
        },
      });

      const response = await request(app)
        .get(`/api/events/${event.id}`)
        .expect(200);

      expect(response.body.title).toBe('Sample Event');
    });
  });
});

Mocking

Use mocks for external dependencies and APIs.
import { vi } from 'vitest';
import axios from 'axios';

vi.mock('axios');

describe('API Service', () => {
  it('should fetch events from API', async () => {
    const mockEvents = [{ id: '1', title: 'Event 1' }];
    (axios.get as any).mockResolvedValue({ data: mockEvents });

    const result = await fetchEvents();
    
    expect(result).toEqual(mockEvents);
    expect(axios.get).toHaveBeenCalledWith('/api/events');
  });
});

Test Coverage

Generate coverage reports to identify untested code:
# Frontend coverage
npm run test -- --coverage

# Backend coverage
cd src/packages/server
npm test -- --coverage
Coverage reports are generated in:
  • Frontend: coverage/
  • Backend: src/packages/server/coverage/
Aim for at least 80% code coverage for critical business logic like payments, ticket minting, and authentication.

Testing Best Practices

Test Behavior, Not Implementation

Focus on what the code does, not how it does it.
// Good
expect(result.total).toBe(105);

// Avoid
expect(internalFunction).toHaveBeenCalled();

Use Descriptive Names

Test names should clearly describe what they test.
it('should calculate total with service fee')
it('should return 401 when token is invalid')
it('should mint NFT and update database')

Arrange-Act-Assert

Structure tests with clear setup, execution, and verification.
// Arrange
const mockData = { ... };

// Act
const result = functionUnderTest(mockData);

// Assert
expect(result).toBe(expected);

Test Edge Cases

Don’t just test the happy path.
it('should handle empty input')
it('should throw error for negative price')
it('should timeout after 30 seconds')

Isolate Tests

Each test should be independent.
beforeEach(() => {
  vi.clearAllMocks();
});

Mock External Dependencies

Mock APIs, databases, and third-party services.
vi.mock('axios');
vi.mock('@passmint/database');
vi.mock('ethers');

Continuous Integration

Tests should run automatically in CI/CD pipelines:
.github/workflows/test.yml
name: Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Run frontend tests
        run: npm test
      
      - name: Run backend tests
        run: |
          cd src/packages/server
          npm test
      
      - name: Upload coverage
        uses: codecov/codecov-action@v3

Debugging Tests

Focus on one test while debugging:
// Use .only to run just this test
it.only('should calculate price', () => {
  // ...
});
Or run by file:
npm test -- events.test.ts
Add console logs to see what’s happening:
it('should process order', () => {
  console.log('Input:', mockOrder);
  const result = processOrder(mockOrder);
  console.log('Result:', result);
  expect(result.status).toBe('completed');
});
Debug tests in VS Code:
  1. Add breakpoint in test file
  2. Run “Debug Test” from test file
  3. Step through execution
Or use Node debugger:
node --inspect-brk node_modules/.bin/vitest

Common Test Patterns

Testing Async Code

it('should fetch data asynchronously', async () => {
  const result = await fetchData();
  expect(result).toBeDefined();
});

Testing Error Handling

it('should throw error for invalid input', () => {
  expect(() => validatePrice(-10)).toThrow('Price must be positive');
});

it('should reject promise on API error', async () => {
  await expect(fetchWithError()).rejects.toThrow('API Error');
});

Testing React Hooks

import { renderHook, act } from '@testing-library/react';
import { useTickets } from './useTickets';

it('should load tickets', async () => {
  const { result } = renderHook(() => useTickets(eventId));
  
  expect(result.current.loading).toBe(true);
  
  await waitFor(() => {
    expect(result.current.loading).toBe(false);
    expect(result.current.tickets).toHaveLength(5);
  });
});

Next Steps

Local Setup

Set up your development environment

Database Schema

Learn about data models to test

Contributing

Contribution guidelines

Vitest Docs

Official Vitest documentation
Always run tests before committing code. Set up a pre-commit hook to enforce this:
# .husky/pre-commit
npm test

Build docs developers (and LLMs) love