diff --git a/src/Laravel/Constraint/Bus/WasHandled.php b/src/Laravel/Constraint/Bus/WasHandled.php index 51e0644..a1295dd 100644 --- a/src/Laravel/Constraint/Bus/WasHandled.php +++ b/src/Laravel/Constraint/Bus/WasHandled.php @@ -78,13 +78,13 @@ protected function matches(mixed $other): bool default => $other, }; $handler = $this->handler($command); - $assertInvocation = match ($this->givenOrDerivedObjectConstraints($other)) { - [] => null, - default => $this->assertHandlerWasInvokedWithCommandConstraints(...), - }; try { - Assert::assertThat($handler, new WasCalled($assertInvocation, $this->times)); + $handler->assert(new WasCalled(function (object $handled) use ($command): void { + foreach ($this->givenOrDerivedObjectConstraints($command) as $constraint) { + Assert::assertThat($handled, $constraint); + } + }, $this->times)); } catch (ExpectationFailedException $expectationFailed) { $this->additionalFailureDescriptions[] = $expectationFailed->getMessage(); @@ -107,13 +107,6 @@ private function handler(object $command): SpyCallable return $handler; } - private function assertHandlerWasInvokedWithCommandConstraints(object $command): void - { - foreach ($this->givenOrDerivedObjectConstraints($command) as $constraint) { - Assert::assertThat($command, $constraint); - } - } - public function toString(): string { return 'command was handled'; diff --git a/src/Laravel/Constraint/Bus/WasHandledTest.php b/src/Laravel/Constraint/Bus/WasHandledTest.php index 73a9b7c..cd13903 100644 --- a/src/Laravel/Constraint/Bus/WasHandledTest.php +++ b/src/Laravel/Constraint/Bus/WasHandledTest.php @@ -4,8 +4,10 @@ namespace Craftzing\TestBench\Laravel\Constraint\Bus; +use Craftzing\TestBench\PHPUnit\Constraint\Callables\WasCalled; use Craftzing\TestBench\PHPUnit\Constraint\Objects\DeriveConstraintsFromObjectUsingFakes; use Craftzing\TestBench\PHPUnit\Constraint\Objects\DeriveConstraintsFromObjectUsingReflection; +use Craftzing\TestBench\PHPUnit\Constraint\Spy; use Craftzing\TestBench\PHPUnit\DataProviders\QuantableConstraint; use Craftzing\TestBench\PHPUnit\Doubles\SpyCallable; use Illuminate\Support\Facades\Bus; @@ -269,7 +271,7 @@ public function itCanDeriveCommandConstraintsFromCommandObjectsUsingCustomImplem } #[Test] - public function itFailsWhenNotHandledWithDerivedCommandConstraints(): void + public function itFailsWhenHandledButNotWithDerivedCommandConstraints(): void { $command = new stdClass(); WasHandled::using(fn (stdClass $command): string => 'handled', $this->app); @@ -293,4 +295,34 @@ public function itPassesWhenDispatchedWithDerivedCommandConstraints(): void $this->assertThat($command, new WasHandled()); } + + #[Test] + public function itDerivesConstraintsFromExpectedCommandsAndMatchesItAgainstActualCommands(): void + { + $expected = $this->command(['id' => 'expected']); + $actual = $this->command(['id' => 'actual']); + $constraint = Spy::passing(); + $deriveConstraints = new DeriveConstraintsFromObjectUsingFakes([$constraint]); + WasHandled::deriveConstraintsFromObjectUsing($deriveConstraints); + WasHandled::using(fn (stdClass $command): string => 'handled', $this->app); + Bus::dispatch($actual); + + $this->assertThat($expected, new WasHandled()); + + $deriveConstraints->invoke->assert(new WasCalled()->withSame($expected)); + $deriveConstraints->invoke->assert(new WasCalled()->never()->withSame($actual)); + $constraint->matches->assert(new WasCalled()->withSame($actual)); + $constraint->matches->assert(new WasCalled()->never()->withSame($expected)); + } + + private function command(array $properties): stdClass + { + $command = new stdClass(); + + foreach ($properties as $property => $value) { + $command->{$property} = $value; + } + + return $command; + } } diff --git a/src/Laravel/Constraint/Eloquent/ModelComparatorTest.php b/src/Laravel/Constraint/Eloquent/ModelComparatorTest.php index f426a5a..0d5c82a 100644 --- a/src/Laravel/Constraint/Eloquent/ModelComparatorTest.php +++ b/src/Laravel/Constraint/Eloquent/ModelComparatorTest.php @@ -16,6 +16,9 @@ use function compact; +/** + * @codeCoverageIgnore + */ final class ModelComparatorTest extends TestCase { public static function accepts(): iterable diff --git a/src/PHPUnit/Constraint/Callables/WasCalled.php b/src/PHPUnit/Constraint/Callables/WasCalled.php index 694faef..396d82b 100644 --- a/src/PHPUnit/Constraint/Callables/WasCalled.php +++ b/src/PHPUnit/Constraint/Callables/WasCalled.php @@ -11,6 +11,7 @@ use Craftzing\TestBench\PHPUnit\Doubles\SpyCallable; use InvalidArgumentException; use Override; +use PHPUnit\Framework\Assert; use PHPUnit\Framework\Constraint\Constraint; use PHPUnit\Framework\ExpectationFailedException; @@ -26,6 +27,17 @@ public function __construct( public readonly ?int $times = null, ) {} + public function withSame(mixed ...$expected): self + { + return new self(function (mixed ...$actual) use ($expected): void { + Assert::assertCount(count($expected), $actual); + + foreach ($actual as $key => $value) { + Assert::assertSame($expected[$key], $value); + } + }, $this->times); + } + public function times(int $count): self { return new self($this->assertInvocation, $count); diff --git a/src/PHPUnit/Constraint/Objects/DeriveConstraintsFromObjectUsingFakes.php b/src/PHPUnit/Constraint/Objects/DeriveConstraintsFromObjectUsingFakes.php index baf91fa..e2d6dbb 100644 --- a/src/PHPUnit/Constraint/Objects/DeriveConstraintsFromObjectUsingFakes.php +++ b/src/PHPUnit/Constraint/Objects/DeriveConstraintsFromObjectUsingFakes.php @@ -4,16 +4,21 @@ namespace Craftzing\TestBench\PHPUnit\Constraint\Objects; +use Craftzing\TestBench\PHPUnit\Doubles\SpyCallable; use PHPUnit\Framework\Constraint\Callback; final readonly class DeriveConstraintsFromObjectUsingFakes implements DeriveConstraintsFromObject { + public SpyCallable $invoke; + /** * @param array<\PHPUnit\Framework\Constraint\Constraint> $constraints */ public function __construct( public array $constraints, - ) {} + ) { + $this->invoke = new SpyCallable(); + } public static function failingConstraints(): self { @@ -31,6 +36,8 @@ public static function passingConstraints(): self public function __invoke(object $object): array { + $this->invoke->__invoke($object); + return $this->constraints; } } diff --git a/src/PHPUnit/Constraint/Spy.php b/src/PHPUnit/Constraint/Spy.php new file mode 100644 index 0000000..a82a42c --- /dev/null +++ b/src/PHPUnit/Constraint/Spy.php @@ -0,0 +1,35 @@ +matches->__invoke($other); + } + + public function toString(): string + { + return 'constraint matches as Spy was configured to fail'; + } +} diff --git a/src/PHPUnit/Doubles/SpyCallable.php b/src/PHPUnit/Doubles/SpyCallable.php index 2258905..77622e5 100644 --- a/src/PHPUnit/Doubles/SpyCallable.php +++ b/src/PHPUnit/Doubles/SpyCallable.php @@ -7,6 +7,7 @@ use Craftzing\TestBench\PHPUnit\AssertsConstraints; use function call_user_func_array; +use function func_get_args; use function is_callable; final class SpyCallable @@ -22,8 +23,9 @@ public function __construct( public readonly mixed $return = null, ) {} - public function __invoke(mixed ...$arguments): mixed + public function __invoke(): mixed { + $arguments = func_get_args(); $this->invocations[] = new CallableInvocation(...$arguments); if (is_callable($this->return)) {