hasProperty($property)) { $reflection = $reflection->getParentClass(); if (!$reflection) { throw new \Exception("Property {$property} does not exist"); } } $prop = $reflection->getProperty($property); $prop->setAccessible(true); $prop->setValue($object, $value); } protected function tearDown(): void { Mockery::close(); parent::tearDown(); } protected function setUp(): void { parent::setUp(); $this->mockOutlet = $this->createMock(Outlet::class); $this->mockOutletTax = $this->getMockBuilder(OutletTax::class) ->addMethods(['where']) ->getMock(); $this->mockOutletImage = $this->createMock(OutletImage::class); $this->mockOutletOperatingDay = $this->getMockBuilder(OutletOperatingDay::class) ->addMethods(['where']) ->getMock(); $this->mockOutletOperatingHour = $this->getMockBuilder(OutletOperatingHour::class) ->addMethods(['where']) ->getMock(); $this->mockOutletOperatingHoursException = $this->getMockBuilder(OutletOperatingHoursException::class) ->addMethods(['where']) ->getMock(); $this->mockSettingTax = $this->createMock(SettingTax::class); $this->controller = new OutletController(); $this->setProtectedOrPrivateProperty($this->controller, 'outlet', $this->mockOutlet); $this->setProtectedOrPrivateProperty($this->controller, 'outletTax', $this->mockOutletTax); $this->setProtectedOrPrivateProperty($this->controller, 'outletImage', $this->mockOutletImage); $this->setProtectedOrPrivateProperty($this->controller, 'outletOperatingDays', $this->mockOutletOperatingDay); $this->setProtectedOrPrivateProperty($this->controller, 'outletOperatingHours', $this->mockOutletOperatingHour); $this->setProtectedOrPrivateProperty($this->controller, 'outletOperatingHoursExceptions', $this->mockOutletOperatingHoursException); $this->setProtectedOrPrivateProperty($this->controller, 'settingTax', $this->mockSettingTax); $this->setProtectedOrPrivateProperty($this->controller, 'request', $this->createMock(IncomingRequest::class)); $this->setProtectedOrPrivateProperty($this->controller, 'response', service('response')); } public function testIndexReturnsOutlets() { $outlets = [ ['id' => 1, 'title' => 'Outlet 1'], ['id' => 2, 'title' => 'Outlet 2'], ]; $this->mockOutlet->method('getOperatingHoursWithDaysList')->willReturn($outlets); $result = $this->controller->index(); $this->assertEquals(200, $result->getStatusCode()); $data = json_decode($result->getBody(), true); $this->assertIsArray($data); $this->assertEquals(200, $data['status']); $this->assertEquals($outlets, $data['result']); } public function testIndexReturnsNoOutlets() { $this->mockOutlet->method('getOperatingHoursWithDaysList')->willReturn([]); $result = $this->controller->index(); $this->assertEquals(200, $result->getStatusCode()); $data = json_decode($result->getBody(), true); $this->assertIsArray($data); $this->assertEquals(400, $data['status']); $this->assertEquals('No outlet data found.', $data['result']); } public function testShowReturnsOutlet() { $outlet = ['id' => 1, 'title' => 'Outlet 1']; $this->mockOutlet->method('getOperatingHoursWithDays')->with(1)->willReturn($outlet); $result = $this->controller->show(1); $this->assertEquals(200, $result->getStatusCode()); $data = json_decode($result->getBody(), true); $this->assertIsArray($data); $this->assertEquals(200, $data['status']); $this->assertEquals($outlet, $data['result']); } public function testShowReturnsNotFound() { $this->mockOutlet->method('getOperatingHoursWithDays')->with(99)->willReturn(null); $result = $this->controller->show(99); $this->assertEquals(200, $result->getStatusCode()); $data = json_decode($result->getBody(), true); $this->assertIsArray($data); $this->assertEquals(400, $data['status']); $this->assertEquals('No outlet data found.', $data['result']); } public function testDeleteOutletNotFound() { $this->mockOutlet->method('find')->with(99)->willReturn(null); $result = $this->controller->delete(99); $this->assertEquals(200, $result->getStatusCode()); $data = json_decode($result->getBody(), true); $this->assertIsArray($data); $this->assertEquals(400, $data['status']); $this->assertEquals('No outlet data found.', $data['result']); } public function testDeleteSuccess() { // Create mock builder objects for chainable methods $mockTaxBuilder = $this->getMockBuilder(\CodeIgniter\Database\BaseBuilder::class) ->disableOriginalConstructor() ->onlyMethods(['delete']) ->getMock(); $mockDayBuilder = $this->getMockBuilder(\CodeIgniter\Database\BaseBuilder::class) ->disableOriginalConstructor() ->onlyMethods(['delete']) ->getMock(); $mockHourBuilder = $this->getMockBuilder(\CodeIgniter\Database\BaseBuilder::class) ->disableOriginalConstructor() ->onlyMethods(['delete']) ->getMock(); $mockExceptionBuilder = $this->getMockBuilder(\CodeIgniter\Database\BaseBuilder::class) ->disableOriginalConstructor() ->onlyMethods(['delete']) ->getMock(); $this->mockOutlet->method('find')->with(1)->willReturn(['id' => 1]); $this->mockOutlet->expects($this->once())->method('delete')->with(1); // Setup chainable method expectations $this->mockOutletTax->method('where')->with('outlet_id', 1)->willReturn($mockTaxBuilder); $mockTaxBuilder->expects($this->once())->method('delete')->willReturn(true); $this->mockOutletOperatingDay->method('where')->with('outlet_id', 1)->willReturn($mockDayBuilder); $mockDayBuilder->expects($this->once())->method('delete')->willReturn(true); $this->mockOutletOperatingHour->method('where')->with('outlet_id', 1)->willReturn($mockHourBuilder); $mockHourBuilder->expects($this->once())->method('delete')->willReturn(true); $this->mockOutletOperatingHoursException->method('where')->with('outlet_id', 1)->willReturn($mockExceptionBuilder); $mockExceptionBuilder->expects($this->once())->method('delete')->willReturn(true); $result = $this->controller->delete(1); $this->assertEquals(200, $result->getStatusCode()); $data = json_decode($result->getBody(), true); $this->assertIsArray($data); $this->assertEquals(200, $data['status']); $this->assertEquals('Outlet deleted successfully.', $data['result']); } public function testUpdatePasswordOutletNotFound() { $this->mockOutlet->method('find')->with(99)->willReturn(null); $result = $this->controller->updatePassword(99); $this->assertEquals(200, $result->getStatusCode()); $data = json_decode($result->getBody(), true); $this->assertIsArray($data); $this->assertEquals(400, $data['status']); $this->assertEquals('No outlet data found.', $data['result']); } public function testCreateValidationFails() { $controller = $this->getMockBuilder(OutletController::class) ->onlyMethods(['validate']) ->enableOriginalConstructor() ->getMock(); $controller->method('validate')->willReturn(false); $validator = $this->getMockBuilder(\CodeIgniter\Validation\Validation::class) ->disableOriginalConstructor() ->getMock(); $validator->method('getErrors')->willReturn(['title' => 'Required']); $this->setProtectedOrPrivateProperty($controller, 'validator', $validator); $this->setProtectedOrPrivateProperty($controller, 'outlet', $this->mockOutlet); $this->setProtectedOrPrivateProperty($controller, 'outletTax', $this->mockOutletTax); $this->setProtectedOrPrivateProperty($controller, 'outletImage', $this->mockOutletImage); $this->setProtectedOrPrivateProperty($controller, 'outletOperatingDays', $this->mockOutletOperatingDay); $this->setProtectedOrPrivateProperty($controller, 'outletOperatingHours', $this->mockOutletOperatingHour); $this->setProtectedOrPrivateProperty($controller, 'outletOperatingHoursExceptions', $this->mockOutletOperatingHoursException); $this->setProtectedOrPrivateProperty($controller, 'settingTax', $this->mockSettingTax); $this->setProtectedOrPrivateProperty($controller, 'request', $this->createMock(IncomingRequest::class)); $this->setProtectedOrPrivateProperty($controller, 'response', service('response')); $result = $controller->create(); $this->assertEquals(400, $result->getStatusCode()); // 400 is CodeIgniter's default $data = json_decode($result->getBody(), true); $this->assertIsArray($data); $this->assertArrayHasKey('messages', $data); $this->assertArrayHasKey('title', $data['messages']); } public function testCreateSuccess() { // 1. Mock IncomingRequest $request = $this->createMock(\CodeIgniter\HTTP\IncomingRequest::class); $request->method('getVar')->willReturnMap([ ['title', 'Outlet 1'], ['email', 'outlet1@example.com'], ['phone', '123456789'], ['address', '123 Street'], ['state', 'State'], ['postal_code', '12345'], ['country', 'Country'], ['latitude', '1.234'], ['longitude', '5.678'], ['password', 'password'], ['outlet_delivery_coverage', '10'], ['outlet_tax', json_encode([1, 2])], ['outlet_operating_days', json_encode([['0' => ['is_operated' => 1]]])], ['outlet_operating_hours', json_encode([['0' => [['start_time' => '09:00', 'end_time' => '18:00']]]])], ['outlet_operating_hours_exceptions', json_encode([])], ]); $request->method('getFiles')->willReturn([]); // 2. Mock Controller with validate() overridden $controller = $this->getMockBuilder(\App\Controllers\Backend\OutletController::class) ->onlyMethods(['validate']) ->getMock(); $controller->method('validate')->willReturn(true); // 3. Mock all Models and insert returns $mockOutlet = $this->createMock(\App\Models\Outlet::class); $mockOutlet->method('insert')->willReturn(1); $mockOutlet->method('find')->willReturn(['id' => 1, 'title' => 'Outlet 1']); $mockOutletTax = $this->createMock(\App\Models\OutletTax::class); $mockOutletTax->method('insert')->willReturn(true); $mockOutletOperatingDay = $this->createMock(\App\Models\OutletOperatingDay::class); $mockOutletOperatingDay->method('insert')->willReturn(true); $mockOutletOperatingHour = $this->createMock(\App\Models\OutletOperatingHour::class); $mockOutletOperatingHour->method('insert')->willReturn(true); $mockOutletOperatingHoursException = $this->createMock(\App\Models\OutletOperatingHoursException::class); $mockOutletOperatingHoursException->method('insert')->willReturn(true); $mockOutletImage = $this->createMock(\App\Models\OutletImage::class); $mockSettingTax = $this->createMock(\App\Models\SettingTax::class); // 4. Mock DB transaction flow // Mock global DB connection $mockDB = Mockery::mock(\CodeIgniter\Database\BaseConnection::class); $mockDB->shouldReceive('transBegin')->andReturn(true); $mockDB->shouldReceive('transCommit')->andReturn(true); $mockDB->shouldReceive('transRollback')->andReturn(true); $this->setProtectedOrPrivateProperty($controller, 'db', $mockDB); // 5. Inject all mocked dependencies $this->setProtectedOrPrivateProperty($controller, 'request', $request); $this->setProtectedOrPrivateProperty($controller, 'outlet', $mockOutlet); $this->setProtectedOrPrivateProperty($controller, 'outletTax', $mockOutletTax); $this->setProtectedOrPrivateProperty($controller, 'outletImage', $mockOutletImage); $this->setProtectedOrPrivateProperty($controller, 'outletOperatingDays', $mockOutletOperatingDay); $this->setProtectedOrPrivateProperty($controller, 'outletOperatingHours', $mockOutletOperatingHour); $this->setProtectedOrPrivateProperty($controller, 'outletOperatingHoursExceptions', $mockOutletOperatingHoursException); $this->setProtectedOrPrivateProperty($controller, 'settingTax', $mockSettingTax); $this->setProtectedOrPrivateProperty($controller, 'response', service('response')); $this->setProtectedOrPrivateProperty($controller, 'db', $mockDB); // 6. Call the method $response = $controller->create(); // 7. Assert response $this->assertEquals(200, $response->getStatusCode()); $data = json_decode($response->getBody(), true); $this->assertEquals('success', $data['status']); $this->assertArrayHasKey('result', $data); $this->assertEquals('Outlet 1', $data['result']['title']); } public function testUpdateValidationFails() { // Create controller mock that allows original validateData method to be called $controller = $this->getMockBuilder(OutletController::class) ->onlyMethods([]) // Don't mock any methods - we want real validateData behavior ->enableOriginalConstructor() ->getMock(); // Create a proper validator mock $validator = $this->getMockBuilder(\CodeIgniter\Validation\Validation::class) ->disableOriginalConstructor() ->getMock(); $validator->method('getErrors')->willReturn([ 'title' => 'The title field is required.', 'email' => 'The email field is required.', 'phone' => 'The phone field is required.', 'address' => 'The address field is required.', 'state' => 'The state field is required.', 'postal_code' => 'The postal_code field is required.', 'country' => 'The country field is required.', 'latitude' => 'The latitude field is required.', 'longitude' => 'The longitude field is required.', 'outlet_delivery_coverage' => 'The outlet_delivery_coverage field is required.', 'outlet_tax' => 'The outlet_tax field is required.', 'outlet_operating_days' => 'The outlet_operating_days field is required.', 'outlet_operating_hours' => 'The outlet_operating_hours field is required.', ]); // Create request mock with invalid data $mockRequest = $this->getMockBuilder(IncomingRequest::class) ->disableOriginalConstructor() ->getMock(); $invalidData = [ 'title' => '', 'email' => 'invalid-email', 'phone' => '3o4i234234', 'address' => 'dfdgdgdgerfsd', 'state' => 'Fkdfnskf', 'postal_code' => 'fsdf', 'country' => 'fsdfsd', 'latitude' => 'not-number', 'longitude' => 'not-number', 'outlet_delivery_coverage' => 'not-number', 'outlet_tax' => 'sdfsdfs', 'outlet_operating_days' => 'sfsfsd', 'outlet_operating_hours' => 'dsfsdfds', 'outlet_operating_hours_exceptions' => null, 'outlet_images' => null ]; // Fix: getVar() must return full array when called with no argument $mockRequest->method('getVar') ->willReturnCallback(function ($key = null) use ($invalidData) { if ($key === null) { return $invalidData; // full array for validateData() } return $invalidData[$key] ?? null; // or specific key }); // getJSON fallback (optional - may not be used in controller) $mockRequest->method('getJSON') ->willReturn($invalidData); // Inject our mocks $this->setProtectedOrPrivateProperty($controller, 'validator', $validator); $this->setProtectedOrPrivateProperty($controller, 'outlet', $this->mockOutlet); $this->setProtectedOrPrivateProperty($controller, 'outletTax', $this->mockOutletTax); $this->setProtectedOrPrivateProperty($controller, 'outletImage', $this->mockOutletImage); $this->setProtectedOrPrivateProperty($controller, 'outletOperatingDays', $this->mockOutletOperatingDay); $this->setProtectedOrPrivateProperty($controller, 'outletOperatingHours', $this->mockOutletOperatingHour); $this->setProtectedOrPrivateProperty($controller, 'outletOperatingHoursExceptions', $this->mockOutletOperatingHoursException); $this->setProtectedOrPrivateProperty($controller, 'settingTax', $this->mockSettingTax); $this->setProtectedOrPrivateProperty($controller, 'request', $mockRequest); $this->setProtectedOrPrivateProperty($controller, 'response', service('response')); // Mock outlet exists $this->mockOutlet->method('find')->with(1)->willReturn(['id' => 1]); // Call the update method $result = $controller->update(1); // Assertions $this->assertEquals(400, $result->getStatusCode()); $data = json_decode($result->getBody(), true); $this->assertIsArray($data); $this->assertArrayHasKey('status', $data); $this->assertEquals('400', $data['status']); // $this->assertArrayHasKey('message', $data); // $this->assertEquals('The title field is required.', $data['message']); } public function testUpdateOutletNotFound() { $this->mockOutlet->method('find')->with(99)->willReturn(null); $result = $this->controller->update(99); $this->assertEquals(200, $result->getStatusCode()); $data = json_decode($result->getBody(), true); $this->assertIsArray($data); $this->assertEquals(400, $data['status']); $this->assertEquals('No outlet data found.', $data['result']); } }