In this article I would like to describe how you can refactor a method that contains multiple if-statements by creating objects with a single responsibility, each making their own assertions. Precondition checks might for example validate the arguments that are passed to the function, prevent failure scenarios, or make assertions about the state of the data in your system.

For each precondition check, we can can now create an implementation of the CourseAttemptRejectionContract: use App\Assertions\Contracts\CourseAttemptRejectionContract; use App\Models\Course; use Carbon\Carbon; class CourseExpired implements CourseAttemptRejectionContract { public function handle(Course $course): bool { return Carbon::parse($course->expiration_date)->lt(Carbon::now())} public function message(): string { return 'Course expired'; }} use App\Assertions\Contracts\CourseAttemptRejectionContract; use App\Models\Course; class TooManyAttemptsForCourse implements CourseAttemptRejectionContract { public function handle(Course $course): bool { return count($course->attempts) >= $course->max_attempts; } public function message(): string { return 'Too many attempts'; }} use App\Assertions\Contracts\CourseAttemptRejectionContract; use App\Models\Course; class CourseAlreadyFinished implements CourseAttemptRejectionContract { public function handle(Course $course): bool { return $course->hasCertificate(); } public function message(): string { return 'Course already finished'; }}

It is possible to create a stub object for the precondition checks, for example one that always returns true: use App\Assertions\Contracts\CourseAttemptRejectionContract; use App\Models\Course; class CourseAttemptRejectionStub implements CourseAttemptRejectionContract { public function handle(Course $course): bool { return true; } public function message(): string { return 'Failure'; }} use PHPUnit\Framework\TestCase; use App\Actions\CreateCourseAttempt; use App\Exceptions\CreateCourseAttemptException; use App\Models\Course; use App\Repositories\CourseAttemptRepository; use Tests\Stubs\CourseAttemptRejectionStub; class CreateCourseAttemptTest extends TestCase { public function testActionPreconditionFailed(): void { $course = new Course(); $courseAttemptRepository = new CourseAttemptRepository(); $createCourseAttemptAction = new CreateCourseAttempt( $courseAttemptRepository, [ CourseAttemptRejectionStub::class, ]); $this->expectException(CreateCourseAttemptException::class); $createCourseAttemptAction->handle($course, ['answer']); } public function testActionSuccessfulWithoutChecks(): void { $course = new Course(); $courseAttemptRepository = new CourseAttemptRepository(); $createCourseAttemptAction = new CreateCourseAttempt( $courseAttemptRepository, [ ]); $this->assertTrue($createCourseAttemptAction->handle($course, ['answer'])); }}

A unit test for a single precondition check could look like this: use PHPUnit\Framework\TestCase; use App\Assertions\TooManyAttemptsForCourse; class CreateCourseAttemptTest extends TestCase { public function testTooManyAttemptsPreconditionFailed(): void { $course = new Course([ 'attempts' => 3, 'max_attempts' => 3, ]); $condition = new TooManyAttemptsForCourse(); $this->assertEquals(true, $rejection->handle($course)); } public function testTooManyAttemptsPreconditionPassed(): void { $course = new Course([ 'attempts' => 3, 'max_attempts' => 3, ]); $condition = new TooManyAttemptsForCourse(); $this->assertEquals(true, $rejection->handle($course)); }}
Newsletter

Get the latest Laravel/PHP jobs, events and curated articles straight to your inbox, once a week

Fathom Analytics | Fast, simple and privacy-focused website analytics. Fathom Analytics | Fast, simple and privacy-focused website analytics.
Achieve superior email deliverability with ToastMail! Our AI-driven tool warms up inboxes, monitors reputation, and ensures emails reach their intended destination. Sign up today for a spam-free future. Achieve superior email deliverability with ToastMail! Our AI-driven tool warms up inboxes, monitors reputation, and ensures emails reach their intended destination. Sign up today for a spam-free future.
Community Partners