$name, 'data' => $data, 'options' => $options]; } } } namespace Tests\App\Controllers\View { use App\Controllers\View\ReimbursementController; use CodeIgniter\Database\BaseBuilder; use CodeIgniter\Database\BaseConnection; use CodeIgniter\Database\BaseResult; use CodeIgniter\Test\CIUnitTestCase; use Config\Services; use App\Models\UserModel; class TestableReimbursementController extends ReimbursementController { public function __construct() { // Skip the real parent constructor so we can inject mocks. } public function setDb(BaseConnection $db): self { $this->db = $db; return $this; } public function setUserModel(UserModel $model): self { $this->userModel = $model; return $this; } } class ReimbursementFakeRenderer { private array $data = []; private array $lastRender = []; public function setData(?array $data = null): self { $this->data = $data ?? []; return $this; } public function render(string $view, array $data = [], $options = null) { $this->lastRender = [ 'view' => $view, 'data' => $data ?: $this->data, 'options' => $options, ]; return 'fake-rendered:' . $view; } public function getLastRender(): array { return $this->lastRender; } } class ReimbursementControllerTest extends CIUnitTestCase { private TestableReimbursementController $controller; private ReimbursementFakeRenderer $renderer; protected function setUp(): void { parent::setUp(); $this->renderer = new ReimbursementFakeRenderer(); Services::injectMock('renderer', $this->renderer); $result = $this->createMock(BaseResult::class); $result->method('getResultArray')->willReturn([ [ 'id' => 5, 'expense_id' => 5, 'expense_amount' => 15.5, 'description' => 'Markers', 'expense_receipt' => 'uploads/path/expense_receipt.pdf', 'retailor' => 'Office Store', 'purchased_by' => 11, 'purchaser_firstname' => 'Amina', 'purchaser_lastname' => 'Teacher', ], [ 'id' => 6, 'expense_id' => 6, 'expense_amount' => 22.0, 'description' => 'Paper', 'expense_receipt' => 'uploads/receipt_path.pdf', 'retailor' => 'Supply Shop', 'purchased_by' => 12, 'purchaser_firstname' => 'Omar', 'purchaser_lastname' => 'Admin', ], ]); $builder = $this->createMock(BaseBuilder::class); $builder->method('select')->willReturnSelf(); $builder->method('join')->willReturnSelf(); $builder->method('where')->willReturnSelf(); $builder->method('orWhere')->willReturnSelf(); $builder->method('groupStart')->willReturnSelf(); $builder->method('groupEnd')->willReturnSelf(); $builder->method('orderBy')->willReturnSelf(); $builder->method('get')->willReturn($result); $db = $this->createMock(BaseConnection::class); $db->method('table')->willReturn($builder); $this->controller = new TestableReimbursementController(); $this->controller->setDb($db); $userModel = $this->getMockBuilder(UserModel::class) ->disableOriginalConstructor() ->addMethods(['select', 'join', 'where']) ->onlyMethods(['findAll']) ->getMock(); $userModel->method('select')->willReturnSelf(); $userModel->method('join')->willReturnSelf(); $userModel->method('where')->willReturnSelf(); $userModel->method('findAll')->willReturn([]); $this->controller->setUserModel($userModel); } protected function tearDown(): void { Services::resetSingle('renderer'); parent::tearDown(); } public function testUnderProcessingBuildsViewWithExpenses() { $result = $this->controller->underProcessing(); if (is_array($result)) { $rendered = $result; } else { $this->assertSame('fake-rendered:reimbursements/under_processing', $result); $rendered = $this->renderer->getLastRender(); } $this->assertSame('reimbursements/under_processing', $rendered['view']); $pendingItems = $rendered['data']['pendingItems']; $this->assertCount(2, $pendingItems); $first = $pendingItems[0]; $this->assertSame(site_url('receipts/expense_receipt.pdf'), $first['receipt_url']); $this->assertSame('Amina Teacher', $first['purchased_by']); $second = $pendingItems[1]; $this->assertSame(site_url('receipts/receipt_path.pdf'), $second['receipt_url']); $this->assertSame('Omar Admin', $second['purchased_by']); } } }