Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Service Container Extension Laravel Package

friends-of-behat/service-container-extension

Declare custom Symfony DI services in Behat without writing a full extension. Import XML/YAML/PHP service definition files via behat.yml so your contexts and helpers can be wired through the Behat service container.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Steps

  1. Installation:

    composer require friends-of-behat/service-container-extension --dev
    
  2. Configure Behat: Add the extension to behat.yml and specify service import paths:

    # behat.yml
    default:
        extensions:
            FriendsOfBehat\ServiceContainerExtension:
                imports:
                    - "%paths.base%/features/bootstrap/config/services.yml"
    
  3. Define a Service: Create a YAML file (or XML/PHP) to declare services:

    # features/bootstrap/config/services.yml
    services:
        app.test_user_repository:
            class: App\Repositories\TestUserRepository
            arguments:
                - "@database.connection.testing"
    
  4. Inject into Context: Use the service in a Behat context:

    use Behat\Behat\Context\Context;
    use App\Repositories\TestUserRepository;
    
    class FeatureContext implements Context {
        private TestUserRepository $userRepository;
    
        public function __construct(TestUserRepository $userRepository) {
            $this->userRepository = $userRepository;
        }
    
        /**
         * @Given I have a test user
         */
        public function iHaveATestUser() {
            $this->userRepository->createTestUser();
        }
    }
    
  5. Run Tests: Execute Behat as usual:

    ./vendor/bin/behat
    

First Use Case

Mocking External APIs: Define a mock service to replace a real API client in tests:

# features/bootstrap/config/services.yml
services:
    app.mock_payment_gateway:
        class: App\Services\MockPaymentGateway
        calls:
            - [setExpectedResponse, ["success"]]

Inject into a context to test payment flows without hitting a real service:

class PaymentContext {
    public function __construct(private MockPaymentGateway $gateway) {}

    /**
     * @When I process a payment
     */
    public function iProcessAPayment() {
        $this->gateway->charge(100);
    }
}

Implementation Patterns

Usage Patterns

1. Service Definition Formats

Leverage familiar Laravel-like syntax for service definitions:

  • YAML (Recommended for readability):
    services:
        app.test_service:
            class: App\Services\TestService
            arguments:
                - "@database.connection.testing"
                - "%env(TEST_API_KEY)%"
            calls:
                - [configure, ["test_mode"]]
    
  • XML (For complex configurations):
    <container>
        <services>
            <service id="app.mock_logger" class="App\Services\MockLogger">
                <argument type="service" id="monolog.logger.test" />
            </service>
        </services>
    </container>
    
  • PHP (For dynamic definitions):
    $container->setDefinition('app.test_factory', new Definition(
        App\Factories\TestUserFactory::class,
        [new Reference('database.connection.testing')]
    ));
    

2. Service Injection

  • Constructor Injection (Preferred):
    class MyContext {
        public function __construct(private TestService $service) {}
    }
    
  • Method Injection (For optional dependencies):
    class MyContext {
        private ?TestService $service;
    
        public function setTestService(TestService $service) {
            $this->service = $service;
        }
    }
    
    Configure in behat.yml:
    extensions:
        FriendsOfBehat\ServiceContainerExtension:
            services:
                app.my_context:
                    class: App\Context\MyContext
                    calls:
                        - [setTestService, ["@app.test_service"]]
    

3. Environment-Specific Services

Use Laravel’s environment variables or Behat’s parameters:

# features/bootstrap/config/services.yml
services:
    app.test_db_connection:
        class: Illuminate\Database\Connection
        factory: ["db", "connection"]
        arguments:
            - "testing"
            - "%env(DB_TESTING_CONNECTION)%"

4. Reusing Laravel Services

Bridge Laravel’s container with Behat’s (caution: avoid conflicts):

# features/bootstrap/config/services.yml
services:
    app.test_mailer:
        class: App\Services\TestMailer
        factory: ["app", "make"]
        arguments:
            - App\Services\TestMailer::class

Workflows

Test Data Setup

Define a factory service and inject it into contexts:

# services.yml
services:
    app.test_user_factory:
        class: App\Factories\TestUserFactory
        arguments:
            - "@database.connection.testing"
class UserContext {
    public function __construct(private TestUserFactory $factory) {}

    /**
     * @Given a test user exists
     */
    public function aTestUserExists() {
        $this->factory->create(['name' => 'Test User']);
    }
}

API Mocking

Create a mock service for external APIs:

# services.yml
services:
    app.mock_stripe:
        class: App\Services\MockStripeClient
        calls:
            - [setTestMode, [true]]
            - [setResponse, ["charge_success"]]

Inject into contexts to simulate API responses:

class PaymentContext {
    public function __construct(private MockStripeClient $stripe) {}

    /**
     * @When I charge $amount
     */
    public function iChargeAmount(float $amount) {
        $this->stripe->charge($amount);
    }
}

Shared Test Utilities

Centralize reusable utilities (e.g., data generators, assertions):

# services.yml
services:
    app.test_assertions:
        class: App\Services\TestAssertions
        shared: false  # Prototype scope for each test
class SharedContext {
    public function __construct(private TestAssertions $assertions) {}

    /**
     * @Then the response should match schema
     */
    public function theResponseShouldMatchSchema(string $schema) {
        $this->assertions->assertJsonSchema($schema);
    }
}

Integration Tips

  1. Leverage Laravel’s Config: Store service paths in config/behat.php:

    return [
        'service_paths' => [
            database_path('config/behat/services.yml'),
            base_path('tests/behat/config/services.yml'),
        ],
    ];
    

    Then reference in behat.yml:

    extensions:
        FriendsOfBehat\ServiceContainerExtension:
            imports:
                - "%behat.service_paths%"
    
  2. Combine with Laravel Testing: Use Laravel’s RefreshDatabase trait alongside Behat:

    use Illuminate\Foundation\Testing\RefreshDatabase;
    use App\Repositories\TestUserRepository;
    
    class FeatureContext {
        public function __construct(private TestUserRepository $users) {}
    
        /**
         * @BeforeScenario
         */
        public function resetDatabase() {
            // Laravel's RefreshDatabase will handle this
        }
    }
    
  3. Type-Hinting in IDE: Add service definitions to PHPStan’s services.php for autocompletion:

    return [
        'services' => [
            App\Services\TestService::class => FriendsOfBehat\ServiceContainerExtension\ServiceContainerExtension::class,
        ],
    ];
    
  4. CI/CD Optimization: Cache service definitions in CI to speed up test runs:

    # .github/workflows/tests.yml
    jobs:
        test:
            steps:
                - uses: actions/cache@v3
                  with:
                    path: vendor/friends-of-behat/service-container-extension
                    key: ${{ runner.os }}-behat-services-${{ hashFiles('features/bootstrap/config/**') }}
    

Gotchas and Tips

Pitfalls

  1. Service Not Found Errors:

    • Cause: Misspelled service ID or incorrect import path in behat.yml.
    • Fix: Verify paths are absolute or use %paths.base%:
      imports:
          - "%paths.base%/features/bootstrap/config/services.yml"
      
    • Debug: Enable Behat’s debug mode:
      ./vendor/bin/behat --debug
      
  2. Circular Dependencies:

    • Cause: Services A and B reference each other.
    • Fix: Use setPublic(false) in PHP definitions or avoid circular references in YAML/XML.
  3. Singleton vs. Prototype Scope:

    • Cause: Services shared across tests when they should be fresh per test.
    • Fix: Explicitly set scope in YAML:
      services:
          app.test_service:
              class: App\Services\TestService
              shared: false  # Prototype scope
      
  4. Laravel Container Conflicts:

    • Cause: Accidentally redefining Laravel services in Behat’s container.
    • Fix: Prefix Behat services with app. or behat. to avoid collisions:
      services:
          behat.test_service:  # Avoids clash with Laravel
      
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
codifyo/ts-generator-bundle
andydefer/laravel-cluster
testo/fiber
mintobit/jobqueue
a4sex/maintenance-bundle
a4sex/entity-date-update
a4sex/client-identifier
a4sex/base-utilites
a4sex/key-value-storage
a4sex/micro-status
chilldev/dependency-injection-extra
datinglibre/datinglibre-app-api
biberltd/corebundle
bricre/symfony-bundle-test
biberltd/logbundle
dominium/http-adapter-bundle
dominium/google-analytics
a4sex/auto-clean-entity
christhompsontldr/laravel-inky
spatie/mailcoach-vapor