My App
CourseKitBackend

Testing

In-memory adapters, factories, fixtures, and assertion helpers for testing

Overview

The @hfu.digital/coursekit-nestjs/testing subpath provides everything you need to test CourseKit integrations without a real database.

import {
    InMemoryTimetableEventStorage,
    InMemoryRoomStorage,
    InMemoryInstructorStorage,
    InMemoryGroupStorage,
    InMemoryAvailabilityStorage,
    InMemoryAcademicPeriodStorage,
    InMemoryCourseStorage,
    InMemoryLocationDistanceStorage,
    createTestEvent,
    createTestRoom,
    expectNoConflicts,
    expectConflict,
} from '@hfu.digital/coursekit-nestjs/testing';

In-Memory Adapters

Drop-in replacements for Prisma adapters that store data in memory:

AdapterReplaces
InMemoryTimetableEventStoragePrismaTimetableEventAdapter
InMemoryRoomStoragePrismaRoomAdapter
InMemoryInstructorStoragePrismaInstructorAdapter
InMemoryGroupStoragePrismaGroupAdapter
InMemoryAvailabilityStoragePrismaAvailabilityAdapter
InMemoryAcademicPeriodStoragePrismaAcademicPeriodAdapter
InMemoryCourseStoragePrismaCourseAdapter
InMemoryLocationDistanceStoragePrismaLocationDistanceAdapter

Test Module Setup

import { Test } from '@nestjs/testing';
import { CourseKitModule } from '@hfu.digital/coursekit-nestjs';
import {
    InMemoryTimetableEventStorage,
    InMemoryRoomStorage,
    InMemoryInstructorStorage,
    InMemoryGroupStorage,
    InMemoryAvailabilityStorage,
    InMemoryAcademicPeriodStorage,
    InMemoryCourseStorage,
} from '@hfu.digital/coursekit-nestjs/testing';

const module = await Test.createTestingModule({
    imports: [
        CourseKitModule.register({
            eventStorage: new InMemoryTimetableEventStorage(),
            roomStorage: new InMemoryRoomStorage(),
            instructorStorage: new InMemoryInstructorStorage(),
            groupStorage: new InMemoryGroupStorage(),
            availabilityStorage: new InMemoryAvailabilityStorage(),
            periodStorage: new InMemoryAcademicPeriodStorage(),
            courseStorage: new InMemoryCourseStorage(),
        }),
    ],
}).compile();

Factories

Factory functions create test entities with sensible defaults. Pass overrides for specific fields:

import {
    createTestEvent,
    createTestException,
    createTestRoom,
    createTestInstructor,
    createTestGroup,
    createTestCourse,
    createTestAvailability,
    createTestPeriod,
} from '@hfu.digital/coursekit-nestjs/testing';

const event = createTestEvent({
    title: 'Math 101',
    recurrenceRule: 'FREQ=WEEKLY;BYDAY=MO',
});

const room = createTestRoom({
    name: 'Lecture Hall A',
    capacity: 200,
    building: 'Main Building',
});

const instructor = createTestInstructor({
    name: 'Prof. Smith',
    email: 'smith@university.edu',
});

const exception = createTestException({
    eventId: event.id,
    type: 'cancelled',
    originalDate: new Date('2026-03-09'),
});

Fixtures

Pre-built schedule scenarios for common test cases:

import {
    simpleSchoolWeek,
    universitySemester,
    edgeCaseSchedule,
} from '@hfu.digital/coursekit-nestjs/testing';
FixtureDescription
simpleSchoolWeekA basic Mon-Fri school schedule with 3 rooms and 5 instructors
universitySemesterA full semester with recurring lectures, labs, and exam periods
edgeCaseScheduleEdge cases: back-to-back events, midnight crossings, DST transitions

EventSpy

Capture domain events emitted during tests:

import { EventSpy } from '@hfu.digital/coursekit-nestjs/testing';
import { DOMAIN_EVENTS } from '@hfu.digital/coursekit-nestjs';

const spy = new EventSpy(eventEmitter);

// ... perform operations ...

const createdEvents = spy.getEvents(DOMAIN_EVENTS.EVENT_CREATED);
expect(createdEvents).toHaveLength(1);
expect(createdEvents[0].event.title).toBe('Math 101');

Assertion Helpers

Fluent assertion helpers for conflict check results:

import {
    expectNoConflicts,
    expectConflict,
    expectConflictCount,
    expectConflictInvolves,
} from '@hfu.digital/coursekit-nestjs/testing';

// Assert no conflicts
expectNoConflicts(result);

// Assert a specific conflict type exists
expectConflict(result, 'instructor-double-book');

// Assert exact conflict count
expectConflictCount(result, 2);

// Assert a conflict involves specific entities
expectConflictInvolves(result, 'instructor-double-book', ['instructor-1']);

Full Example

import { Test } from '@nestjs/testing';
import { CourseKitModule, ConflictService, type TimetableEvent } from '@hfu.digital/coursekit-nestjs';
import {
    InMemoryTimetableEventStorage,
    InMemoryRoomStorage,
    InMemoryInstructorStorage,
    InMemoryGroupStorage,
    InMemoryAvailabilityStorage,
    InMemoryAcademicPeriodStorage,
    InMemoryCourseStorage,
    createTestEvent,
    createTestRoom,
    expectConflict,
} from '@hfu.digital/coursekit-nestjs/testing';

describe('Conflict Detection', () => {
    let conflicts: ConflictService;
    let eventStorage: InMemoryTimetableEventStorage;

    beforeEach(async () => {
        eventStorage = new InMemoryTimetableEventStorage();
        const module = await Test.createTestingModule({
            imports: [
                CourseKitModule.register({
                    eventStorage,
                    roomStorage: new InMemoryRoomStorage(),
                    instructorStorage: new InMemoryInstructorStorage(),
                    groupStorage: new InMemoryGroupStorage(),
                    availabilityStorage: new InMemoryAvailabilityStorage(),
                    periodStorage: new InMemoryAcademicPeriodStorage(),
                    courseStorage: new InMemoryCourseStorage(),
                }),
            ],
        }).compile();

        conflicts = module.get(ConflictService);
    });

    it('should detect room double-booking', async () => {
        const room = createTestRoom({ name: 'Room A' });
        const event1 = createTestEvent({ title: 'Event 1', roomId: room.id });
        const event2 = createTestEvent({ title: 'Event 2', roomId: room.id });

        await eventStorage.create(event1);
        await eventStorage.create(event2);

        const result = await conflicts.check(event1, {
            start: new Date('2026-03-01'),
            end: new Date('2026-03-31'),
        });

        expectConflict(result, 'room-overlap');
    });
});

On this page