Skip to main content

Testing Guide

This guide covers testing practices and standards for HMP development.

Testing Philosophy​

  • Write tests for all new features
  • Maintain high test coverage (>80%)
  • Tests should be readable and maintainable
  • Test behavior, not implementation

Test Types​

Unit Tests​

Test individual functions and classes in isolation.

// services/reservation-service.test.ts
describe('ReservationService', () => {
let service: ReservationService;
let mockRepository: jest.Mocked<ReservationRepository>;
let mockEventBus: jest.Mocked<EventBus>;

beforeEach(() => {
mockRepository = createMockRepository();
mockEventBus = createMockEventBus();
service = new ReservationService(mockRepository, mockEventBus);
});

describe('create', () => {
it('should create a reservation with valid data', async () => {
const input = {
guestId: 'guest_123',
arrivalDate: new Date('2026-02-15'),
departureDate: new Date('2026-02-18'),
rooms: [{ roomTypeId: 'rt_deluxe', adults: 2 }],
};

mockRepository.create.mockResolvedValue({
id: 'res_123',
status: 'CONFIRMED',
...input,
});

const result = await service.create(input);

expect(result.id).toBe('res_123');
expect(result.status).toBe('CONFIRMED');
expect(mockRepository.create).toHaveBeenCalledWith(input);
expect(mockEventBus.publish).toHaveBeenCalledWith(
'reservation.created',
expect.any(Object),
);
});

it('should throw error when no availability', async () => {
mockRepository.checkAvailability.mockResolvedValue(false);

await expect(service.create(validInput)).rejects.toThrow(
NoAvailabilityError,
);
});
});
});

Integration Tests​

Test multiple components working together.

// tests/integration/reservations.test.ts
describe('Reservations API', () => {
let app: Express;
let db: Database;

beforeAll(async () => {
db = await setupTestDatabase();
app = createApp(db);
});

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

beforeEach(async () => {
await db.clear();
await seedTestData(db);
});

describe('POST /api/v1/reservations', () => {
it('should create a reservation', async () => {
const response = await request(app)
.post('/api/v1/reservations')
.set('Authorization', `Bearer ${testToken}`)
.set('X-Property-ID', 'prop_test')
.send({
guest_id: 'guest_123',
arrival_date: '2026-02-15',
departure_date: '2026-02-18',
rooms: [{ room_type_id: 'rt_deluxe', adults: 2 }],
});

expect(response.status).toBe(201);
expect(response.body.data).toMatchObject({
status: 'CONFIRMED',
guest: { id: 'guest_123' },
});

// Verify in database
const reservation = await db.reservation.findUnique({
where: { id: response.body.data.id },
});
expect(reservation).not.toBeNull();
});

it('should return 400 for invalid dates', async () => {
const response = await request(app)
.post('/api/v1/reservations')
.set('Authorization', `Bearer ${testToken}`)
.set('X-Property-ID', 'prop_test')
.send({
guest_id: 'guest_123',
arrival_date: '2026-02-20',
departure_date: '2026-02-18', // Before arrival
rooms: [{ room_type_id: 'rt_deluxe', adults: 2 }],
});

expect(response.status).toBe(400);
expect(response.body.error).toBe('validation_error');
});
});
});

End-to-End Tests​

Test complete user flows.

// tests/e2e/check-in-flow.test.ts
describe('Guest Check-In Flow', () => {
it('should complete full check-in process', async () => {
// Create reservation
const reservation = await createTestReservation({
arrivalDate: today(),
status: 'CONFIRMED',
});

// Login as front desk agent
const session = await login('frontdesk@hotel.com', 'password');

// Navigate to arrivals
const arrivals = await session.get('/api/v1/front-desk/arrivals');
expect(arrivals.body.data).toContainEqual(
expect.objectContaining({ id: reservation.id }),
);

// Start check-in
const checkIn = await session.post(
`/api/v1/front-desk/check-in/${reservation.id}`,
{
room_id: 'room_405',
id_document: {
type: 'PASSPORT',
number: 'J1234567',
},
},
);

expect(checkIn.status).toBe(200);
expect(checkIn.body.data.status).toBe('CHECKED_IN');

// Verify room status updated
const room = await session.get('/api/v1/rooms/room_405');
expect(room.body.data.fo_status).toBe('OCCUPIED');
});
});

Test Utilities​

Factory Functions​

// tests/factories/reservation.factory.ts
export function createTestReservation(
overrides: Partial<Reservation> = {},
): Reservation {
return {
id: `res_${randomId()}`,
confirmationNumber: `TEST${Date.now()}`,
status: 'CONFIRMED',
guestId: 'guest_test',
arrivalDate: addDays(new Date(), 7),
departureDate: addDays(new Date(), 10),
totalAmount: 15000,
...overrides,
};
}

export function createTestGuest(overrides: Partial<Guest> = {}): Guest {
return {
id: `guest_${randomId()}`,
firstName: 'Test',
lastName: 'Guest',
email: `test_${randomId()}@example.com`,
...overrides,
};
}

Mock Utilities​

// tests/mocks/repository.mock.ts
export function createMockRepository<T>(): jest.Mocked<Repository<T>> {
return {
findById: jest.fn(),
findMany: jest.fn(),
create: jest.fn(),
update: jest.fn(),
delete: jest.fn(),
};
}

// tests/mocks/event-bus.mock.ts
export function createMockEventBus(): jest.Mocked<EventBus> {
return {
publish: jest.fn().mockResolvedValue(undefined),
subscribe: jest.fn(),
};
}

Test Database​

// tests/setup/database.ts
export async function setupTestDatabase(): Promise<Database> {
const db = new Database({
url: process.env.TEST_DATABASE_URL,
});

await db.migrate();
return db;
}

export async function seedTestData(db: Database): Promise<void> {
// Create test property
await db.property.create({
data: {
id: 'prop_test',
code: 'TEST',
name: 'Test Hotel',
},
});

// Create room types
await db.roomType.createMany({
data: [
{ id: 'rt_standard', code: 'STD', name: 'Standard' },
{ id: 'rt_deluxe', code: 'DLX', name: 'Deluxe' },
],
});

// Create test rooms
await db.room.createMany({
data: [
{ id: 'room_101', roomNumber: '101', roomTypeId: 'rt_standard' },
{ id: 'room_405', roomNumber: '405', roomTypeId: 'rt_deluxe' },
],
});
}

Running Tests​

Commands​

# Run all tests
npm test

# Run unit tests only
npm run test:unit

# Run integration tests
npm run test:integration

# Run e2e tests
npm run test:e2e

# Run with coverage
npm run test:coverage

# Run specific test file
npm test -- reservation-service.test.ts

# Run tests matching pattern
npm test -- --grep "create reservation"

# Watch mode
npm test -- --watch

Configuration​

// jest.config.js
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
roots: ['<rootDir>/src', '<rootDir>/tests'],
testMatch: ['**/*.test.ts', '**/*.spec.ts'],
collectCoverageFrom: [
'src/**/*.ts',
'!src/**/*.d.ts',
'!src/**/index.ts',
],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80,
},
},
};

Testing Best Practices​

Arrange-Act-Assert Pattern​

it('should calculate correct total', () => {
// Arrange
const reservation = createTestReservation({
nights: 3,
ratePerNight: 5000,
});

// Act
const total = calculateTotal(reservation);

// Assert
expect(total).toBe(15000);
});

Test Isolation​

// DO: Each test is independent
describe('ReservationService', () => {
beforeEach(() => {
// Fresh setup for each test
service = new ReservationService(mockRepo);
});

it('test 1', () => { });
it('test 2', () => { });
});

// DON'T: Tests depend on each other
let reservation;
it('creates reservation', () => {
reservation = service.create(data);
});
it('updates reservation', () => {
service.update(reservation.id, newData); // Fails if first test fails
});

Descriptive Test Names​

// DO: Describe expected behavior
it('should throw NoAvailabilityError when all rooms are booked', () => {});
it('should apply early bird discount for bookings 30+ days ahead', () => {});

// DON'T: Vague names
it('works', () => {});
it('test create', () => {});

Test Edge Cases​

describe('calculateRate', () => {
it('should handle single night stays', () => {});
it('should handle maximum length stays', () => {});
it('should handle zero amount', () => {});
it('should handle decimal amounts', () => {});
it('should handle date boundary (midnight)', () => {});
});

Mocking Guidelines​

When to Mock​

  • External services (APIs, databases in unit tests)
  • Time-dependent functions
  • Random values
  • System resources (file system, network)

When Not to Mock​

  • Pure functions
  • Simple data transformations
  • Internal collaborators (in integration tests)

Mocking Examples​

// Mock external API
jest.mock('../services/payment-gateway', () => ({
processPayment: jest.fn().mockResolvedValue({ success: true }),
}));

// Mock time
jest.useFakeTimers();
jest.setSystemTime(new Date('2026-02-15'));

// Mock random
jest.spyOn(Math, 'random').mockReturnValue(0.5);