container = $this->createMock(Container::class); $this->reflectionProvider = $this->createMock(ReflectionProvider::class); $this->logger = $this->createMock(Logger::class); $this->resolver = new MiddlewareDependencyResolver( $this->reflectionProvider, $this->container, $this->logger ); } public function test_resolves_simple_middleware_without_dependencies(): void { // Create a simple middleware class for testing $middlewareClass = SimpleTestMiddleware::class; // Mock that the class exists and is instantiable $this->reflectionProvider ->method('isInstantiable') ->willReturn(true); // Mock that it has no constructor parameters $this->reflectionProvider ->method('getMethodParameters') ->willReturn([]); // Mock that it implements HttpMiddleware $this->reflectionProvider ->method('implementsInterface') ->with( $this->callback(fn ($className) => $className instanceof ClassName), HttpMiddleware::class ) ->willReturn(true); $result = $this->resolver->resolve([$middlewareClass]); $this->assertCount(1, $result->getMiddlewares()); $this->assertContains($middlewareClass, $result->getMiddlewares()); } public function test_filters_out_middleware_with_missing_dependencies(): void { $middlewareClass = MiddlewareWithDependencies::class; // Mock that the class exists and is instantiable $this->reflectionProvider ->method('isInstantiable') ->willReturn(true); // Mock constructor parameter that requires a dependency $parameterMock = $this->createMock(\ReflectionParameter::class); $typeMock = $this->createMock(\ReflectionType::class); $parameterMock->method('getType')->willReturn($typeMock); $parameterMock->method('isOptional')->willReturn(false); $parameterMock->method('allowsNull')->willReturn(false); $typeMock->method('isBuiltin')->willReturn(false); $typeMock->method('getName')->willReturn('SomeService'); $this->reflectionProvider ->method('getMethodParameters') ->willReturn([$parameterMock]); // Mock that the dependency is NOT available in container $this->container ->method('has') ->with('SomeService') ->willReturn(false); // Mock that it implements HttpMiddleware $this->reflectionProvider ->method('implementsInterface') ->willReturn(true); // Expect warning to be logged $this->logger ->expects($this->once()) ->method('warning') ->with($this->stringContains('Missing dependencies for MiddlewareWithDependencies: SomeService')); $result = $this->resolver->resolve([$middlewareClass]); // Should be filtered out due to missing dependency $this->assertCount(0, $result->getMiddlewares()); } public function test_includes_middleware_with_available_dependencies(): void { $middlewareClass = MiddlewareWithDependencies::class; // Mock that the class exists and is instantiable $this->reflectionProvider ->method('isInstantiable') ->willReturn(true); // Mock constructor parameter that requires a dependency $parameterMock = $this->createMock(\ReflectionParameter::class); $typeMock = $this->createMock(\ReflectionType::class); $parameterMock->method('getType')->willReturn($typeMock); $parameterMock->method('isOptional')->willReturn(false); $parameterMock->method('allowsNull')->willReturn(false); $typeMock->method('isBuiltin')->willReturn(false); $typeMock->method('getName')->willReturn('SomeService'); $this->reflectionProvider ->method('getMethodParameters') ->willReturn([$parameterMock]); // Mock that the dependency IS available in container $this->container ->method('has') ->with('SomeService') ->willReturn(true); // Mock that it implements HttpMiddleware $this->reflectionProvider ->method('implementsInterface') ->willReturn(true); $result = $this->resolver->resolve([$middlewareClass]); // Should be included because dependency is available $this->assertCount(1, $result->getMiddlewares()); $this->assertContains($middlewareClass, $result->getMiddlewares()); } public function test_logs_information_about_resolution_process(): void { $middlewareClass = SimpleTestMiddleware::class; // Mock successful resolution $this->reflectionProvider->method('isInstantiable')->willReturn(true); $this->reflectionProvider->method('getMethodParameters')->willReturn([]); $this->reflectionProvider->method('implementsInterface')->willReturn(true); // Expect debug and info logs $this->logger ->expects($this->once()) ->method('debug') ->with($this->stringContains('Starting resolution for 1 middlewares')); $this->logger ->expects($this->once()) ->method('info') ->with($this->stringContains('Resolution completed with 1 middlewares')); $this->resolver->resolve([$middlewareClass]); } public function test_handles_non_existent_middleware_class(): void { $nonExistentClass = 'NonExistentMiddleware'; // Expect warning to be logged $this->logger ->expects($this->once()) ->method('warning') ->with($this->stringContains('Class not found: NonExistentMiddleware')); $result = $this->resolver->resolve([$nonExistentClass]); // Should return empty result $this->assertCount(0, $result->getMiddlewares()); } } // Test middleware classes final class SimpleTestMiddleware implements HttpMiddleware { public function __invoke(MiddlewareContext $context, Next $next, RequestStateManager $stateManager): MiddlewareContext { return $next($context); } } final class MiddlewareWithDependencies implements HttpMiddleware { public function __construct( private object $someService ) { } public function __invoke(MiddlewareContext $context, Next $next, RequestStateManager $stateManager): MiddlewareContext { return $next($context); } }