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; } private function bestehend(): Mahlzeit { $jetzt = $this->jetzt(); return new Mahlzeit(5, 'Altes', 'alte Notiz', [Wochentag::Mo, Wochentag::Di], 3, MahlzeitStatus::Geplant, $jetzt, $jetzt, null); } public function testExecuteAktualisiertNameNotizUndWochentage(): void { $erfasstesModell = null; $manager = $this->createMock(MahlzeitManagerInterface::class); $manager->expects(self::once())->method('find')->with(5)->willReturn($this->bestehend()); $manager->expects(self::once()) ->method('update') ->willReturnCallback(function (Mahlzeit $m) use (&$erfasstesModell) { $erfasstesModell = $m; return $m; }); $events = $this->createMock(EventDispatcherInterface::class); $events->expects(self::once()) ->method('dispatch') ->with(self::equalTo(new MahlzeitUpdatedEvent(id: 5, name: 'Neues'))); $useCase = new MahlzeitUpdateUseCase($this->tx(), $manager, new MahlzeitFactory(), $events, $this->clock()); $response = $useCase->execute(5, new MahlzeitUpdateRequest(name: 'Neues', notiz: 'neu', wochentage: [Wochentag::Fr])); self::assertSame('Neues', $erfasstesModell->name); self::assertSame('neu', $erfasstesModell->notiz); self::assertSame([Wochentag::Fr], $erfasstesModell->wochentage()); self::assertInstanceOf(MahlzeitUpdateResponse::class, $response); self::assertSame(5, $response->id); self::assertSame('Neues', $response->mahlzeit->name); } public function testExecuteLässtUndefinierteFelderUnerändert(): void { $alt = $this->bestehend(); $erfasstesModell = null; $manager = $this->createMock(MahlzeitManagerInterface::class); $manager->expects(self::once())->method('find')->with(5)->willReturn($alt); $manager->expects(self::once()) ->method('update') ->willReturnCallback(function (Mahlzeit $m) use (&$erfasstesModell) { $erfasstesModell = $m; return $m; }); $events = $this->createMock(EventDispatcherInterface::class); $events->expects(self::once())->method('dispatch'); $useCase = new MahlzeitUpdateUseCase($this->tx(), $manager, new MahlzeitFactory(), $events, $this->clock()); $useCase->execute(5, new MahlzeitUpdateRequest(name: null, notiz: null, wochentage: null)); self::assertSame('Altes', $erfasstesModell->name, 'nicht geliefertes Feld bleibt unverändert'); self::assertSame('alte Notiz', $erfasstesModell->notiz); self::assertSame([Wochentag::Mo, Wochentag::Di], $erfasstesModell->wochentage()); } public function testExecuteLeertWochentageMitEmptyArray(): void { $erfasstesModell = null; $manager = $this->createMock(MahlzeitManagerInterface::class); $manager->expects(self::once())->method('find')->with(5)->willReturn($this->bestehend()); $manager->expects(self::once()) ->method('update') ->willReturnCallback(function (Mahlzeit $m) use (&$erfasstesModell) { $erfasstesModell = $m; return $m; }); $events = $this->createMock(EventDispatcherInterface::class); $events->expects(self::once())->method('dispatch'); $useCase = new MahlzeitUpdateUseCase($this->tx(), $manager, new MahlzeitFactory(), $events, $this->clock()); $useCase->execute(5, new MahlzeitUpdateRequest(wochentage: [])); self::assertSame([], $erfasstesModell->wochentage()); } public function testExecuteWirft404WennNichtVorhanden(): void { $manager = $this->createStub(MahlzeitManagerInterface::class); $manager->method('find')->willThrowException(new MahlzeitNotFoundException(99)); $events = $this->createMock(EventDispatcherInterface::class); $events->expects(self::never())->method('dispatch'); $useCase = new MahlzeitUpdateUseCase($this->tx(), $manager, new MahlzeitFactory(), $events, $this->clock()); $this->expectException(MahlzeitNotFoundException::class); try { $useCase->execute(99, new MahlzeitUpdateRequest(name: 'X')); } finally { // Erwartete Exception oben. } } }