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 code Compatible with Jest API
Jest Comprehensive testing for backend With Supertest for API testing
React Testing Library Component testing utilities User-centric testing approach
Supertest HTTP assertion library For testing Express APIs
Running Tests
Frontend Tests (Vitest)
Run frontend tests using Vitest:
Run All Tests
Watch Mode
Run Specific File
Coverage Report
Backend Tests (Jest)
Run backend tests from the server package:
Run All Tests
Watch Mode
E2E Tests
With Coverage
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.
Utility Function Test
Component Test
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.
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.
Mock Axios
Mock Prisma
Mock Blockchain
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:
Add breakpoint in test file
Run “Debug Test” from test file
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