Weave 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

Behat extension that lets you declare your own services inside the Behat container without writing an extension, by importing XML, YAML, or PHP service definition files into behat.yml.

View on GitHub
Deep Wiki
Context7

Getting Started

Establish the boundary first. This is a Behat extension that plugs into Behat's Symfony ContainerBuilder. It has nothing to do with Illuminate\Container, AppServiceProvider, or Laravel's test helpers. If your stack is Pest/PHPUnit with $this->mock() and .env.testing, this package adds nothing. It earns its place only when Behat is already your acceptance runner and you need a test-scoped service registered without hand-writing a custom Behat extension.

Minimal steps

  1. Install as a dev dependency:
    composer require friends-of-behat/service-container-extension --dev
    
  2. Enable it in behat.yml — the sole config key is imports::
    default:
        extensions:
            FriendsOfBehat\ServiceContainerExtension:
                imports:
                    - "features/bootstrap/config/services.yml"
    
    Paths resolve relative to the behat.yml file.
  3. Write a service definition (XML, YAML, and PHP are all accepted):
    # features/bootstrap/config/services.yml
    services:
        _defaults:
            public: true
        test.payment_gateway:
            class: Tests\Double\FakePaymentGateway
    
  4. Type-hint it into your context:
    final class CheckoutContext implements Context
    {
        public function __construct(private FakePaymentGateway $gateway) {}
    }
    

First realistic use case. You have a Behat checkout suite hitting a slow, flaky sandbox payment gateway. Register a FakePaymentGateway here, inject it into CheckoutContext, and assert against it directly. That is the entire pitch: no extension boilerplate, no new scattered across step definitions.

Where to look first. Read the README's three format examples — they cover the whole surface area. There is no debug command, no console integration, and no schema beyond imports. If those examples match your need, you have finished reading.

Implementation Patterns

Use imports: as a manifest, grouped by concern.

imports:
    - "features/bootstrap/config/services/mocks.yml"
    - "features/bootstrap/config/services/fixtures.yml"
    - "features/bootstrap/config/services/clients.php"

Move to PHP definitions when you need logic. YAML/XML handle a static class plus arguments; anything conditional belongs in PHP. The $container variable is handed to you, with full access to Definition, Reference, Alias, and parameter bags:

use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;

$container->setDefinition(
    'test.crm',
    (new Definition(FakeCrm::class))
        ->setArguments([new Reference('test.http_log')])
        ->setPublic(true)
);

This is where you point an S3Client at LocalStack, build from getenv(), or register a factory closure.

Compose, don't flatten. A test.checkout_helper that takes test.payment_gateway and test.crm as Reference arguments lets each step definition depend on one collaborator instead of three. Swapping a double becomes a one-line YAML edit and step definitions stay thin.

Alias interfaces to implementations. If a helper is type-hinted against an interface, define the interface alias and point it at today's concrete double. Contexts stay decoupled from whatever class the services file currently registers.

Layer imports per profile. Behat profiles inherit extensions, so you can override for CI:

ci:
    extensions:
        FriendsOfBehat\ServiceContainerExtension:
            imports:
                - "features/bootstrap/config/services/ci-clients.yml"

Swap a fake for a recording proxy here, and back again locally.

Reset state in @BeforeScenario. Services are singletons by default; a recording double will be polluted by scenario two. Add a reset() method and call it from a hook, or register as a prototype/factory that yields a fresh instance per resolution.

Keep it test-scoped. Register mocks, fixtures, test utilities, environment-specific clients, and teardown helpers. Do not register application domain services here expecting them to surface in app() later — they won't, and you'll be maintaining two divergent containers.

Gotchas and Tips

The container is not Laravel's. app() inside a Behat context will throw or hand back the wrong thing. If a Behat service genuinely needs Eloquent or a Laravel service, bootstrap Laravel manually inside a PHP definition and register a narrow adapter. A TestDatabaseBridge is fine; a UserRepository borrowed from the app is not.

Public is not the default. Since Symfony 5, services are private unless declared otherwise, and Behat resolves contexts via $container->get(). Any service injected into a context must be public. Set _defaults: public: true in YAML or ->setPublic(true) per definition. This is the single most common "service not found" cause.

Singletons leak across scenarios. Behat doesn't reset the container between scenarios, so an accumulating fake passes three tests and fails the fourth. Debug by dumping state from a @BeforeScenario hook, then reset explicitly.

The container compiles once at boot. You cannot register a service from a @BeforeSuite hook — by the time it runs, the container is frozen. Decide the file layout up front; retrofitting means editing a file, not calling a method.

No lifecycle, no hooks, no step filtering. This package is pure registration. The moment you need a @BeforeTestRun hook, a custom formatter, or a step-definition filter, you need a real Behat extension. Notice that wall early — switching over is cheaper the sooner you do it.

Version matrix is a hard gate. v2.0.0 requires PHP ^8.3, Symfony ^7.4||^8.0, Behat ^3.31||^4.0. Older stacks are pinned to v1.x, which is frozen and — per its changelog — dropped CrossContainerExtension support. Pin explicitly and check your CI matrix before merging.

CI can silently skip all of this if the test job runs composer install --no-dev. Behat won't boot, and the failure looks like "tests didn't run" rather than "extension missing." Verify pipeline install flags, not just your laptop.

Duplicate service IDs. Behat registers its own (mink.*, symfony.*, context services). Colliding IDs resolve by compile-time merge order — sometimes a silent override, sometimes ServiceNotFoundException on the first scenario. Prefix your services (test., acme.).

%env()% resolution is incomplete. Symfony's env processor isn't wired into Behat's container as it is in a full app. Use getenv('FOO') inside a PHP definition rather than expecting %env(FOO)% to resolve.

Debugging. No debug:container equivalent exists. Practical routes: behat --debug --strict -vvv for boot errors; a temporary @BeforeSuite hook dumping your collaborators; or a throwaway script that loads the same behat.yml through Behat's ContainerBuilder and inspects definitions. That last one pays for itself the first time you fight a wiring issue.

Reuse signal. If three suites need the same doubles, factor them into features/bootstrap/config/services/shared.yml and import from each profile. If you need shared behavior rather than shared definitions, that's your cue to graduate to a real extension — and your service files carry over, so the migration is mechanical.

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.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo