My App
RoomKitBackend

Testing

Test patterns and in-memory adapters for RoomKit

Overview

RoomKit provides in-memory storage adapters for testing. These adapters implement all 11 storage interfaces without requiring a database, making tests fast and isolated.

import { createInMemoryStorage } from '@roomkit/nestjs/testing';

In-Memory Adapters

The createInMemoryStorage() factory returns a complete set of storage implementations that hold data in memory. All data is reset between test runs when you create a new instance.

import { createInMemoryStorage } from '@roomkit/nestjs/testing';

const storage = createInMemoryStorage();
// storage implements all 11 storage interfaces:
// LocationStorage, RoomStorage, BookingStorage, RecurrenceStorage,
// BlackoutStorage, ConflictStorage, ConfigStorage, AuditStorage,
// PriorityStorage, ExamStorage, BulkOperationStorage

Test Setup Pattern

Use the NestJS Test.createTestingModule with in-memory adapters to create an isolated test module.

import { Test } from '@nestjs/testing';
import {
    RoomKitModule,
    BookingService,
    LocationService,
    RoomService,
} from '@roomkit/nestjs';
import { createInMemoryStorage } from '@roomkit/nestjs/testing';

describe('BookingService', () => {
    let bookingService: BookingService;
    let locationService: LocationService;
    let roomService: RoomService;

    beforeEach(async () => {
        const module = await Test.createTestingModule({
            imports: [
                RoomKitModule.register({
                    storage: createInMemoryStorage(),
                }),
            ],
        }).compile();

        bookingService = module.get(BookingService);
        locationService = module.get(LocationService);
        roomService = module.get(RoomService);
    });

    it('should create and confirm a booking', async () => {
        const location = await locationService.createNode({
            type: 'room',
            displayName: 'Room 101',
        });

        const room = await roomService.create({
            locationNodeId: location.id,
            seatedCapacity: 30,
            examCapacity: 15,
            standingCapacity: 50,
        });

        const booking = await bookingService.create({
            roomId: room.id,
            requesterId: 'user-1',
            title: 'Team Meeting',
            startsAt: new Date('2026-03-02T10:00:00Z'),
            endsAt: new Date('2026-03-02T11:00:00Z'),
            purposeType: 'SEMINAR',
        });

        expect(booking.status).toBe('requested');

        const confirmed = await bookingService.confirm(booking.id, 'admin');
        expect(confirmed.status).toBe('confirmed');
    });
});

Booking Lifecycle Test

Test the full lifecycle of a booking from creation through completion.

it('should complete the full booking lifecycle', async () => {
    const location = await locationService.createNode({
        type: 'room',
        displayName: 'Lecture Hall A',
    });

    const room = await roomService.create({
        locationNodeId: location.id,
        seatedCapacity: 100,
        examCapacity: 50,
        standingCapacity: 150,
    });

    // Create
    const booking = await bookingService.create({
        roomId: room.id,
        requesterId: 'user-1',
        title: 'CS 101 Lecture',
        startsAt: new Date('2026-03-02T09:00:00Z'),
        endsAt: new Date('2026-03-02T10:00:00Z'),
        purposeType: 'LECTURE',
    });
    expect(booking.status).toBe('requested');

    // Confirm
    const confirmed = await bookingService.confirm(booking.id, 'admin');
    expect(confirmed.status).toBe('confirmed');

    // Check in
    const started = await bookingService.checkIn(booking.id, 'user-1');
    expect(started.status).toBe('in_progress');

    // Complete
    const completed = await bookingService.complete(booking.id, 'user-1');
    expect(completed.status).toBe('completed');
});

Conflict Detection Test

Verify that overlapping bookings are detected correctly.

import { BookingConflictError } from '@roomkit/nestjs';

it('should detect conflicts for overlapping bookings', async () => {
    const location = await locationService.createNode({
        type: 'room',
        displayName: 'Room 202',
    });

    const room = await roomService.create({
        locationNodeId: location.id,
        seatedCapacity: 20,
        examCapacity: 10,
        standingCapacity: 30,
    });

    // Create the first booking
    await bookingService.create({
        roomId: room.id,
        requesterId: 'user-1',
        title: 'Morning Meeting',
        startsAt: new Date('2026-03-02T09:00:00Z'),
        endsAt: new Date('2026-03-02T10:00:00Z'),
        purposeType: 'SEMINAR',
    });

    // Attempt to create an overlapping booking
    await expect(
        bookingService.create({
            roomId: room.id,
            requesterId: 'user-2',
            title: 'Overlapping Meeting',
            startsAt: new Date('2026-03-02T09:30:00Z'),
            endsAt: new Date('2026-03-02T10:30:00Z'),
            purposeType: 'SEMINAR',
        }),
    ).rejects.toThrow(BookingConflictError);
});

Recurrence Test

Verify that recurring bookings expand to the expected dates.

import { RecurrenceService } from '@roomkit/nestjs';

it('should create a recurring weekly booking', async () => {
    const module = await Test.createTestingModule({
        imports: [
            RoomKitModule.register({
                storage: createInMemoryStorage(),
            }),
        ],
    }).compile();

    const recurrenceService = module.get(RecurrenceService);
    const locationService = module.get(LocationService);
    const roomService = module.get(RoomService);

    const location = await locationService.createNode({
        type: 'room',
        displayName: 'Seminar Room',
    });

    const room = await roomService.create({
        locationNodeId: location.id,
        seatedCapacity: 25,
        examCapacity: 12,
        standingCapacity: 40,
    });

    const result = await recurrenceService.createRecurringSeries({
        roomId: room.id,
        requesterId: 'user-1',
        title: 'Weekly Standup',
        startsAt: new Date('2026-03-02T09:00:00Z'),
        endsAt: new Date('2026-03-02T09:30:00Z'),
        purposeType: 'SEMINAR',
        recurrence: {
            frequency: 'weekly',
            interval: 1,
            until: new Date('2026-03-30T09:30:00Z'),
        },
    });

    // Expect 5 bookings: Mar 2, 9, 16, 23, 30
    expect(result.bookings).toHaveLength(5);
    expect(result.bookings[0].startsAt).toEqual(new Date('2026-03-02T09:00:00Z'));
    expect(result.bookings[4].startsAt).toEqual(new Date('2026-03-30T09:00:00Z'));
});

Testing with Events

Subscribe to domain events during tests to verify that the correct events are emitted.

import { EventBus } from '@roomkit/nestjs';

it('should emit BookingRequested event on create', async () => {
    const eventBus = module.get(EventBus);
    const events: any[] = [];

    eventBus.on('BookingRequested', (event) => {
        events.push(event);
    });

    const location = await locationService.createNode({
        type: 'room',
        displayName: 'Room 303',
    });

    const room = await roomService.create({
        locationNodeId: location.id,
        seatedCapacity: 15,
        examCapacity: 8,
        standingCapacity: 20,
    });

    const booking = await bookingService.create({
        roomId: room.id,
        requesterId: 'user-1',
        title: 'Event Test',
        startsAt: new Date('2026-03-02T14:00:00Z'),
        endsAt: new Date('2026-03-02T15:00:00Z'),
        purposeType: 'SEMINAR',
    });

    expect(events).toHaveLength(1);
    expect(events[0].payload.id).toBe(booking.id);
    expect(events[0].metadata.triggeredBy).toBeDefined();
});

On this page