serviceExpectingClassLookup('9', ['id' => 9, 'class_name' => '9']); $result = $this->invoke($service, 'nextClass', ['Class 8', '2026-2027']); $this->assertSame(9, (int) $result['id']); } public function testPassedClassNinePromotesToTenWhenGradeTenExists(): void { $service = $this->serviceExpectingClassLookup('10', ['id' => 10, 'class_name' => '10']); $result = $this->invoke($service, 'nextClass', ['Class 9', '2026-2027']); $this->assertSame(10, (int) $result['id']); } public function testClassLookupFallsBackToGlobalRowsWhenSchoolYearSpecificRowIsMissing(): void { $firstBuilder = $this->builderReturning(null); $firstBuilder->expects($this->exactly(2)) ->method('where') ->willReturnSelf(); $fallbackBuilder = $this->builderReturning(['id' => 10, 'class_name' => '10']); $fallbackBuilder->expects($this->once()) ->method('where') ->with('UPPER(class_name)', '10') ->willReturnSelf(); $db = $this->createMock(BaseConnection::class); $db->expects($this->exactly(2)) ->method('table') ->with('classes') ->willReturnOnConsecutiveCalls($firstBuilder, $fallbackBuilder); $db->method('fieldExists')->with('school_year', 'classes')->willReturn(true); $service = new EnrollmentTransitionService($db); $result = $this->invoke($service, 'nextClass', ['Class 9', '2026-2027']); $this->assertSame(10, (int) $result['id']); } private function serviceExpectingClassLookup(string $expectedClassName, array $row): EnrollmentTransitionService { $builder = $this->builderReturning($row); $builder->expects($this->once()) ->method('where') ->with('UPPER(class_name)', $expectedClassName) ->willReturnSelf(); $db = $this->createMock(BaseConnection::class); $db->expects($this->once()) ->method('table') ->with('classes') ->willReturn($builder); $db->method('fieldExists')->with('school_year', 'classes')->willReturn(false); return new EnrollmentTransitionService($db); } private function builderReturning(?array $row): BaseBuilder { $builder = $this->createMock(BaseBuilder::class); $builder->method('orderBy')->willReturnSelf(); $builder->method('limit')->willReturnSelf(); $builder->method('get')->willReturn(new class($row) { public function __construct(private readonly ?array $row) { } public function getRowArray(): ?array { return $this->row; } }); return $builder; } /** * @param list $args */ private function invoke(EnrollmentTransitionService $service, string $method, array $args): mixed { $reflection = new \ReflectionMethod($service, $method); $reflection->setAccessible(true); return $reflection->invokeArgs($service, $args); } }