assertNotified(AccommodationBookingStatus::Inquiry); $this->submitStatusChange(AccommodationBookingStatus::Draft, AccommodationBookingStatus::Inquiry, $bookingService); } public function testTransitionToBookingIssuesAccessLinkAndSendsCustomerEmail(): void { $bookingService = $this->assertNotified(AccommodationBookingStatus::Booking); $this->submitStatusChange(AccommodationBookingStatus::Draft, AccommodationBookingStatus::Booking, $bookingService); } public function testTransitionToDiscardedSendsNothing(): void { $bookingService = $this->assertNotNotified(); $this->submitStatusChange(AccommodationBookingStatus::Inquiry, AccommodationBookingStatus::Discarded, $bookingService); } public function testSavingWithoutStatusChangeSendsNothing(): void { $bookingService = $this->assertNotNotified(); $this->submitStatusChange(AccommodationBookingStatus::Booking, AccommodationBookingStatus::Booking, $bookingService); } private function assertNotified(AccommodationBookingStatus $expected): AccommodationBookingService { $bookingService = $this->createMock(AccommodationBookingService::class); $bookingService->expects(self::once())->method('refreshPriceSnapshot'); $bookingService ->expects(self::once()) ->method('issueAccessLink') ->with(self::callback(static fn (AccommodationBooking $booking) => $expected === $booking->getStatus())); $bookingService->expects(self::once())->method('sendCustomerConfirmationEmail'); return $bookingService; } private function assertNotNotified(): AccommodationBookingService { $bookingService = $this->createMock(AccommodationBookingService::class); $bookingService->expects(self::once())->method('refreshPriceSnapshot'); $bookingService->expects(self::never())->method('issueAccessLink'); $bookingService->expects(self::never())->method('sendCustomerConfirmationEmail'); return $bookingService; } private function submitStatusChange( AccommodationBookingStatus $from, AccommodationBookingStatus $to, AccommodationBookingService $bookingService, ): void { $booking = new AccommodationBooking(); $booking->setStatus($from); // The form is what moves the entity to its new status during handleRequest(). $form = $this->createMock(FormInterface::class); $form->method('handleRequest')->willReturnCallback(static function () use ($booking, $to, $form) { $booking->setStatus($to); return $form; }); $form->method('isSubmitted')->willReturn(true); $form->method('isValid')->willReturn(true); $form->method('has')->willReturn(false); $entityManager = $this->createMock(EntityManagerInterface::class); $entityManager->expects(self::once())->method('flush'); $controller = new TestableEditController( $entityManager, $this->createMock(LoggerInterface::class), $this->createMock(BoardServiceRepository::class), $this->createMock(AdditionalServiceRepository::class), $bookingService, $form, ); $response = $controller->index($booking, Request::create('/admin/accommodation-booking/1/edit', 'POST')); self::assertSame(Response::HTTP_FOUND, $response->getStatusCode()); } } final class TestableEditController extends EditController { public function __construct( EntityManagerInterface $entityManager, LoggerInterface $logger, BoardServiceRepository $boardServiceRepo, AdditionalServiceRepository $additionalServiceRepo, AccommodationBookingService $bookingService, private readonly FormInterface $form, ) { parent::__construct($entityManager, $logger, $boardServiceRepo, $additionalServiceRepo, $bookingService); } protected function createForm(string $type, mixed $data = null, array $options = []): FormInterface { return $this->form; } protected function addFlash(string $type, mixed $message): void { } /** * @param array $parameters */ protected function generateUrl(string $route, array $parameters = [], int $referenceType = 1): string { return '/'.$route.'?'.http_build_query($parameters); } }