container = new DefaultContainer(); $this->executionContext = createTestExecutionContext(); $this->queue = new TestQueue(); $this->logger = new TestLogger(); }); test('command handlers collection returns correct handler', function () { $descriptor = new CommandHandlerDescriptor(TestCommandHandler::class, 'handle', TestCommand::class); $collection = new CommandHandlersCollection($descriptor); $retrieved = $collection->get(TestCommand::class); expect($retrieved)->toBe($descriptor) ->and($retrieved->class)->toBe(TestCommandHandler::class) ->and($retrieved->method)->toBe('handle') ->and($retrieved->command)->toBe(TestCommand::class); }); test('command handlers collection returns null for non-existent command', function () { $collection = new CommandHandlersCollection(); $result = $collection->get('NonExistentCommand'); expect($result)->toBeNull(); }); test('command handler descriptor stores class and method', function () { $descriptor = new CommandHandlerDescriptor('TestClass', 'testMethod', 'TestCommand'); expect($descriptor->class)->toBe('TestClass') ->and($descriptor->method)->toBe('testMethod') ->and($descriptor->command)->toBe('TestCommand'); }); test('no handler found exception contains command class', function () { $commandClass = 'TestCommand'; $exception = NoHandlerFound::forCommand($commandClass); expect($exception->getMessage()) ->toContain($commandClass); }); test('dispatch executes command handler directly', function () { $command = new TestCommand('test-data'); $handler = new TestCommandHandler(); $handlerDescriptor = new CommandHandlerDescriptor(TestCommandHandler::class, 'handle', TestCommand::class); $commandHandlers = new CommandHandlersCollection($handlerDescriptor); $commandBus = new DefaultCommandBus( $commandHandlers, $this->container, $this->executionContext, $this->queue, $this->logger, [] // No middlewares for basic tests ); $this->container->instance(TestCommandHandler::class, $handler); $result = $commandBus->dispatch($command); expect($result)->toBe('Handled: test-data'); }); test('dispatch throws exception when no handler found', function () { $command = new TestCommand('test-data'); $commandHandlers = new CommandHandlersCollection(); // Empty collection $commandBus = new DefaultCommandBus( $commandHandlers, $this->container, $this->executionContext, $this->queue, $this->logger, [] // No middlewares for basic tests ); expect(fn () => $commandBus->dispatch($command)) ->toThrow(NoHandlerFound::class); }); test('should queue attribute is recognized', function () { $command = new QueueableTestCommand('queue-data'); $commandHandlers = new CommandHandlersCollection(); // Empty collection to test queueing $commandBus = new DefaultCommandBus( $commandHandlers, $this->container, $this->executionContext, $this->queue, $this->logger, [] // No middlewares for basic tests ); // For queued commands, the result should be null $result = $commandBus->dispatch($command); expect($result)->toBeNull() ->and($this->queue->wasUsed())->toBeTrue(); }); // Test fixtures class TestCommand { public function __construct( public readonly string $data ) { } } #[ShouldQueue] class QueueableTestCommand { public function __construct( public readonly string $data ) { } } class TestCommandHandler { public function handle(TestCommand $command): string { return 'Handled: ' . $command->data; } } // ExecutionContext is final, so we create a simple instance function createTestExecutionContext(): ExecutionContext { return new ExecutionContext(ContextType::WEB); } class TestQueue implements Queue { private bool $used = false; private array $jobs = []; public function push(object $job): void { $this->used = true; $this->jobs[] = $job; } public function pop(): ?object { return array_shift($this->jobs); } public function wasUsed(): bool { return $this->used; } } class TestLogger implements Logger { public function emergency(string $message, array $context = []): void { } public function alert(string $message, array $context = []): void { } public function critical(string $message, array $context = []): void { } public function error(string $message, array $context = []): void { } public function warning(string $message, array $context = []): void { } public function notice(string $message, array $context = []): void { } public function info(string $message, array $context = []): void { } public function debug(string $message, array $context = []): void { } }