jetzt(); return new Mahlzeit($id, $name, '', [], 1, MahlzeitStatus::Geplant, $jetzt, $jetzt, null); } /** Stub: ruft den übergebenen Closure einfach aus (simuliert die Transaktion). */ private function tx(): TransactionManagerInterface { $tx = $this->createStub(TransactionManagerInterface::class); $tx->method('execute')->willReturnCallback(static fn (\Closure $work): mixed => $work($work)); return $tx; } private function clock(): ClockInterface { $clock = $this->createStub(ClockInterface::class); $clock->method('now')->willReturn($this->jetzt()); return $clock; } public function testExecutePersistiertUndLiefertResponse(): void { $gespeichert = $this->mahlzeitMitId(10, 'Bolognese'); $erfasstesModell = null; $manager = $this->createMock(MahlzeitManagerInterface::class); $manager->expects(self::once()) ->method('create') ->willReturnCallback(function (Mahlzeit $m) use (&$erfasstesModell, $gespeichert) { $erfasstesModell = $m; return $gespeichert; }); $events = $this->createMock(EventDispatcherInterface::class); $events->expects(self::once()) ->method('dispatch') ->with(self::equalTo(new MahlzeitCreatedEvent(id: 10, name: 'Bolognese'))); $useCase = new MahlzeitCreateUseCase($this->tx(), $manager, new MahlzeitFactory(), $events, $this->clock()); $response = $useCase->execute(new MahlzeitCreateRequest(name: 'Bolognese', notiz: 'süß')); self::assertSame('Bolognese', $erfasstesModell->name, 'Übergebenes Modell trägt den Namen aus dem Request'); self::assertSame('süß', $erfasstesModell->notiz); self::assertInstanceOf(MahlzeitCreateResponse::class, $response); self::assertSame(10, $response->id); self::assertSame('Bolognese', $response->mahlzeit->name); } public function testExecuteDispatchtEventNachCommit(): void { $mahlzeit = $this->mahlzeitMitId(1, 'X'); $manager = $this->createStub(MahlzeitManagerInterface::class); $manager->method('create')->willReturn($mahlzeit); $events = $this->createMock(EventDispatcherInterface::class); $events->expects(self::once()) ->method('dispatch') ->with(self::equalTo(new MahlzeitCreatedEvent(id: 1, name: 'X'))); $useCase = new MahlzeitCreateUseCase($this->tx(), $manager, new MahlzeitFactory(), $events, $this->clock()); $useCase->execute(new MahlzeitCreateRequest(name: 'X')); } public function testExecuteDispatchtKeinEventBeiFehler(): void { $manager = $this->createStub(MahlzeitManagerInterface::class); $manager->method('create')->willThrowException(new \RuntimeException('DB down')); $events = $this->createMock(EventDispatcherInterface::class); $events->expects(self::never())->method('dispatch'); $useCase = new MahlzeitCreateUseCase($this->tx(), $manager, new MahlzeitFactory(), $events, $this->clock()); $this->expectException(\RuntimeException::class); try { $useCase->execute(new MahlzeitCreateRequest(name: 'X')); } finally { // Erwartete Exception oben. } } }