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

Behat Laravel Extension Laravel Package

laracasts/behat-laravel-extension

A Behat extension that boots your Laravel application inside feature tests, giving step definitions access to the container, facades, database helpers, and configuration so you can write BDD-style acceptance tests.

View on GitHub
Deep Wiki
Context7

Getting Started

This extension is a thin bootstrapper: it loads your Laravel application (container, config, facades, Eloquent) before Behat scenarios run, so step definitions can use app(), resolve services, and hit the framework directly. It does not provide Mink/browser drivers or HTTP assertion helpers — those are yours to write.

  1. Install it alongside Behat:
composer require --dev laracasts/behat-laravel-extension

Because the last release was 2018, expect to pin versions (behat/behat:^3.4, Laravel 5.x/6.x era). On newer Laravel the Dotenv loading in the extension may need patching — see Gotchas.

  1. Create a dedicated env file, e.g. .env.behat:
APP_ENV=behat
APP_DEBUG=true
DB_CONNECTION=sqlite
DB_DATABASE=/absolute/path/to/database/testing.sqlite
CACHE_DRIVER=array
SESSION_DRIVER=array
QUEUE_DRIVER=sync
  1. Configure behat.yml at the project root. The extension's config keys are env_path and bootstrap_path:
default:
  autoload:
    '': features/bootstrap
  extensions:
    Laracasts\Behat\ServiceContainer\BehatExtension:
      env_path: .env.behat
      bootstrap_path: bootstrap/app.php
  suites:
    default:
      contexts:
        - FeatureContext
  1. Look first at vendor/laracasts/behat-laravel-extension/src — it is small and tells you exactly when the app is booted and what it injects. Then write features/bootstrap/FeatureContext.php:
<?php

use Behat\Behat\Context\Context;

class FeatureContext implements Context
{
    private $response;

    /** @When I request :method :uri */
    public function iRequest($method, $uri)
    {
        $kernel = app(Illuminate\Contracts\Http\Kernel::class);
        $request = Illuminate\Http\Request::create($uri, $method);
        $this->response = $kernel->handle($request);
    }

    /** @Then the response status should be :status */
    public function theResponseStatusShouldBe($status)
    {
        PHPUnit\Framework\Assert::assertSame((int) $status, $this->response->getStatusCode());
    }
}

First use case: a login flow — seed a user with a factory, dispatch POST /login through the HTTP kernel, assert the redirect and that Auth::check() is true. That exercises routing, middleware, validation, and Eloquent in one readable scenario, which is exactly what this extension buys you.

Implementation Patterns

Reuse the booted container in steps. The main value is that every step can call app(Foo::class), use facades (DB, Auth, Artisan), and touch real Eloquent models. Keep step definitions thin and push domain setup into factories/seeders.

Wrap each scenario in a transaction. The extension does not reset the database. Add hooks in your context:

/** @BeforeScenario @database */
public function beginTransaction() { app('db')->beginTransaction(); }

/** @AfterScenario @database */
public function rollBack() { app('db')->rollBack(); }

Because the HTTP kernel runs in the same process, it shares the connection — so the rollback works as long as your code doesn't explicitly commit. Run php artisan migrate:fresh --env=behat once before the suite (in CI or a wrapper script), not in a static @BeforeSuite hook, since static hooks run before Behat initializes contexts.

Authenticate by state, not by UI. For scenarios that aren't about login, use Auth::login($user) or app('auth')->loginUsingId($id) in a @Given I am logged in as :email step; save the browser-level login for the one scenario that actually tests it.

Build a small request/response trait. Store the last response on the context, add steps for status, JSON path, redirect, and session assertions. This is the closest you get to Laravel's MakesHttpRequests without pulling in a TestCase; reusing that trait directly is awkward because it assumes PHPUnit lifecycle methods.

Tag and segment suites. Use @database, @auth, @slow tags so you can run behat --tags='~@slow' locally and the full set in CI. Keep browser-driven scenarios (if you add Mink) in a separate suite with its own base URL.

Call Artisan from steps when a scenario depends on a command (Artisan::call('passport:install')). This works because the console kernel is bootstrapped by the extension; it is much faster than shelling out.

Gotchas and Tips

Maintenance risk is the headline. Last release 2018; it targets Behat 3 and older Laravel. On Laravel 6+, the Dotenv API changed and the extension's manual env loading can fail. If it breaks, the extension point is trivial: replace it with your own @BeforeSuite/context constructor that does require 'vendor/autoload.php'; $app = require 'bootstrap/app.php'; $app->make(Illuminate\Contracts\Console\Kernel::class)->bootstrap(); and sets APP_ENV/env file yourself.

env_path is relative to the working directory, not the config file. Always run Behat from the project root, and never point it at your real .env — you will truncate a development database at some point.

Clear config cache before running. php artisan config:cache bakes env values into bootstrap/cache/config.php, so APP_ENV=behat is ignored and you talk to the wrong database. Add php artisan config:clear to your test script. Same for route cache if routes are environment-dependent.

Container state persists for the whole Behat process. Contexts are instantiated per scenario, but the application, resolved singletons, and static caches are not. If your code caches config or holds state in a singleton, reset it in an @AfterScenario hook, or accept that scenario two may see scenario one's leftovers.

In-memory SQLite is a trap if anything opens a second connection (queue workers in-process, parallel connections). Use a file-backed SQLite database or MySQL for the Behat env. For speed, keep migrations once-per-suite and transactions per scenario.

Debugging: behat -vvv --stop-on-failure, then var_dump(app()->environment()) in a step to confirm which env booted. Check storage/logs/laravel.log — exceptions thrown inside the HTTP kernel are logged there rather than always surfacing as Behat failures. If app() is undefined, the extension did not boot: verify the FQCN in behat.yml and that bootstrap_path exists relative to cwd.

Extension points: if you need explicit injection rather than the global helper, write a Behat\Behat\Context\Initializer\ContextInitializer that passes the application into contexts implementing a LaravelAwareContext interface, or simply grab app() in your base context's constructor and assert it booted. Both are a few lines and decouple you from the package's internals if you later replace it.

Alternatives worth weighing: for new projects, Laravel's HTTP testing, Pest, and Dusk cover this with active maintenance. Behat remains worthwhile when non-developers read the Gherkin or you already have a Behat suite — in that case, budget a day to vendor the bootstrapping logic if the extension doesn't install cleanly on your Laravel version.

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.
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib