make(PaymentMethod::KbzMiniApp); expect($gateway)->toBeInstanceOf(KbzMiniAppGateway::class); }); test('registering a fake gateway swaps the resolved implementation with no call-site changes', function () { $factory = app(PaymentGatewayFactory::class); $factory->register(PaymentMethod::KbzMiniApp, FakePaymentGateway::class); // A call site that only knows about the factory + interface, never the // concrete gateway class — this is exactly what an Action/controller does. $callSite = fn (PaymentGatewayFactory $factory, PaymentMethod $method): PaymentGatewayInterface => $factory->make($method); expect($callSite($factory, PaymentMethod::KbzMiniApp))->toBeInstanceOf(FakePaymentGateway::class); }); test('the factory throws when no gateway is registered for a method', function () { $factory = new PaymentGatewayFactory; expect(fn () => $factory->make(PaymentMethod::KbzMiniApp))->toThrow(RuntimeException::class); }); test('the factory is bound as a singleton', function () { expect(app(PaymentGatewayFactory::class))->toBe(app(PaymentGatewayFactory::class)); });