Final Challenge — Solution Task: Add a destroy(string $id) method to TaskController (auth-checked, returns 204 on success or 404 via TaskNotFoundException), and write a corresponding PHPUnit test using a mocked TaskRepository whose delete() method is asserted to have been called exactly once with the correct ID, using $mockRepo->expects($this->once())->method('delete')->with(5);. ---- src/Controllers/TaskController.php (destroy method added) ---- requireAuth(); try { $this->tasks->find((int)$id); } catch (TaskNotFoundException $e) { jsonResponse(['error' => 'Task not found'], 404); } $this->tasks->delete((int)$id); http_response_code(204); exit; } } ?> ---- tests/TaskControllerTest.php (new test) ---- createMock(TaskRepository::class); // find() must succeed first, so destroy() proceeds to delete() $mockRepo->method('find')->willReturn(['id' => 5, 'title' => 'Test task']); $mockRepo->expects($this->once()) ->method('delete') ->with(5); $controller = new TaskController($mockRepo); $controller->destroy('5'); } } ?> Expected test run output: PHPUnit 10.x . 1 / 1 (100%) OK (1 test, 1 assertion) Notes: - destroy() mirrors the Chapter 7 solution's own destroy() method almost exactly - calling find() first purely to confirm the task exists, then delete(), then a bare 204 status with no JSON body - the only genuine addition here is the requireAuth() call at the top, matching every other endpoint in this capstone's own TaskController. - The mock's find() method must be programmed to return a real value (via willReturn()) rather than left unconfigured - otherwise it would return null by default, and destroy() would never actually reach the delete() call at all, since a real TaskRepository would have thrown TaskNotFoundException instead. - $mockRepo->expects($this->once())->method('delete')->with(5) is a genuinely different kind of assertion from anything used earlier in this course - rather than checking a RETURN value, it verifies delete() was actually CALLED, exactly once, with the argument 5 (an integer, matching the (int)$id cast inside destroy()) - PHPUnit itself fails the test automatically if delete() is never called, or called with the wrong argument, or called more than once. - '5' (a string) is passed into destroy(), matching how a real route parameter always arrives as a string - the (int)$id cast inside destroy() converts it before calling delete(), which is exactly why the mock's own ->with(5) expectation uses the integer 5, not the string '5'.