Skip to content

Testing Courses

This guide covers practical patterns for writing tests in the Course module.

Data Hierarchy

The Course module has a deep model hierarchy. Understanding it helps you create only the data your test actually needs.

Content structure (template):

text
Course
├── introductionLesson (shared, course_id = NULL)
├── Lesson (core, 1:m)
│   └── LessonItem (1:m)
│       └── Quiz (1:m)
│           └── (MultipleChoiceQuestion | OpenEndedQuestion)
└── finalLesson (shared, course_id = NULL)

Scheduling & enrollment:

text
Course
└── CourseSchedule (1:m)
    ├── LessonSchedule (1:m)
    └── CourseEnrollment (1:m)
        ├── CourseEnrollmentProgress (1:1)
        └── QuizAnswer (1:m)

Light vs Realistic Methods

Factory methods follow a two-tier naming convention:

  • Light methods (no prefix) — create the minimum viable structure with uniform types. Fast, good for most tests.
  • realistic methods — mirror actual production data: mixed quiz types, widgets, discussions, correct item counts. Slow, use only when you need production-like variety.

Rule of thumb: start with light methods. Reach for realistic only when your test depends on the variety of real data (e.g., mixed MCQ/OEQ, widget rendering, shared lesson structure).

CourseFactory

Light (fast)Realistic (slow)What it creates
withCoreLessons()withRealisticCoreLessons()Core lessons with items and quizzes
withSharedLessons()withRealisticSharedLessons()Introduction (L0) and final (LF) shared lessons
withCoreAndSharedLessons()withRealisticCoreAndSharedLessons()Both core + shared lessons together

LessonFactory

Light (fast)Realistic (slow)What it creates
sharedLessonZero()realisticSharedLessonZero()Introduction lesson (L0)
sharedFinalLesson()realisticSharedFinalLesson()Final lesson (LF)

Common Setups

Minimal: Course with Core Lessons

Use when you need a course with content but don't care about scheduling or enrollment.

php
use Tests\Factories\Course\CourseFactory;

// With quizzes (default)
$course = CourseFactory::new()
    ->withCoreLessons(lessons: 3, itemsPerLesson: 2, quizzesPerItem: 1)
    ->createOne();

// Without quizzes (faster)
$course = CourseFactory::new()
    ->withCoreLessons(quizzesPerItem: 0)
    ->createOne();

Course with Core + Shared Lessons

Creates core lessons and attaches bare introduction (L0) and final (LF) shared lessons.

php
use Tests\Factories\Course\CourseFactory;

$course = CourseFactory::new()
    ->withCoreAndSharedLessons()
    ->createOne();

// Or compose individually for more control:
$course = CourseFactory::new()
    ->withCoreLessons(lessons: 5)
    ->withSharedLessons()
    ->createOne();

Full Setup: Course → Schedule → Enrollment

Use when you need a Member enrolled in a Course.

php
use Tests\Factories\Course\CourseFactory;
use Tests\Factories\Course\CourseScheduleFactory;
use Tests\Factories\Course\CourseEnrollmentFactory;
use Tests\Factories\Member\MemberFactory;

$course = CourseFactory::new()->withCoreLessons()->createOne();
$schedule = CourseScheduleFactory::new()
    ->withLessonSchedules()
    ->forCourse($course)
    ->createOne();

$enrollment = CourseEnrollmentFactory::new()
    ->for(MemberFactory::new()->withMembership())
    ->forCourseSchedule($schedule)
    ->createOne();

Realistic Course

☢️ Nuclear option. Creates a course with realistic core lessons (mixed MCQ/OEQ, discussions) and shared lessons (widgets, correct item counts) that mirror the actual DB structure.

php
$course = CourseFactory::new()
    ->withRealisticCoreAndSharedLessons()
    ->createOne();

You can also create shared lessons independently via LessonFactory:

php
use Tests\Factories\Course\LessonFactory;

// Introduction lesson: 8 items, 13 MCQs, widgets (self-check, peers online/offline), discussion
$introLesson = LessonFactory::new()->realisticSharedLessonZero()->createOne();

// Final lesson: 4 items, 1 MCQ, widgets (certificate, evaluation, next steps)
$finalLesson = LessonFactory::new()->realisticSharedFinalLesson()->createOne();

Quiz Answers

Create the quiz first, then answer it:

php
use Tests\Factories\Course\QuizFactory;
use Tests\Factories\Course\QuizAnswerFactory;

// Create a quiz (MCQ or OEQ)
$quiz = QuizFactory::new()->mcq()->for($lessonItem)->createOne();

// Correct MCQ answer
QuizAnswerFactory::new()
    ->for($quiz, 'quiz')
    ->forEnrollment($enrollment)
    ->withCorrectAnswer()
    ->createOne();

// Ungraded OEQ answer
QuizAnswerFactory::new()
    ->for($quiz, 'quiz')
    ->forEnrollment($enrollment)
    ->ungraded('My answer text')
    ->createOne();

Shared Lessons in Tests

Non-obvious gotchas:

  • Shared QuizAnswers have course_enrollment_id = NULL.
  • Sync is triggered per-lesson when a Member visits a shared lesson page.

Performance Tips

  • Create only the data your test needs. A bare CourseFactory::new()->createOne() is much faster than withCoreLessons().
  • Use withCoreLessons(quizzesPerItem: 0) when quizzes aren't needed — it's faster.
  • Avoid any realistic method calls unless you specifically need realistic variety — they create many DB records.