Appearance
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.
realisticmethods — 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 thanwithCoreLessons(). - Use
withCoreLessons(quizzesPerItem: 0)when quizzes aren't needed — it's faster. - Avoid any
realisticmethod calls unless you specifically need realistic variety — they create many DB records.