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

Web Api Extension Laravel Package

behat/web-api-extension

Abandoned Behat 3 extension (never had a stable release) that provides testing for JSON APIs. It won't receive further updates, so consider alternatives for new projects; kept here for reference and legacy suites.

View on GitHub
Deep Wiki
Context7

Getting Started

First, the honest framing for a Laravel developer: this is not a Laravel package. It is an abandoned Behat 3 extension that never had a stable release, has zero dependents, and receives no updates. In a Laravel codebase the default day-to-day answer is to use Laravel's own HTTP test helpers ($this->getJson(), assertJson(), assertStatus()) with PHPUnit or Pest. Read this section only if you have a legacy Behat suite to keep alive, or you are explicitly evaluating whether to adopt BDD-style API specs.

Compatibility audit before install. The risk with a Behat 3–era extension in a modern Laravel app is not the extension itself — it's the transitive graph. It will pull behat/behat:^3, which in turn constrains old symfony/* components, and Laravel already pins its own Symfony versions for Console, HttpFoundation, Finder, etc. Add a dev-only constraint and observe the resolver:

composer require --dev --dry-run behat/behat:^3.0 behat/web-api-extension:dev-master

If composer reports conflicts against laravel/framework's Symfony constraints, stop here. Resolving those by force-pinning Symfony versions in a Laravel app is how you break artisan after a routine composer update. Do not do it.

If the graph resolves, install and pin a commit. There is no stable tag, so dev-master will move:

composer require --dev behat/behat:^3.0 behat/web-api-extension:dev-master
# then pin the resolved commit in composer.lock and treat it as frozen

Read doc/index.rst, not the README. The README is a deprecation notice. The authoritative step vocabulary, config keys, and registered services live in that RST file and in src/. The Gherkin in any tutorial (including this one) is illustrative — verify the exact step phrasing against the bundled context class before writing feature files.

Register it and run one happy-path scenario. Create behat.yml at the project root, register the bundled context, and point base_url at a running instance of your Laravel app (php artisan serve on a spare port, or a Sail/docker compose service):

default:
  suites:
    api:
      contexts:
        - Behat\WebApiExtension\Context\WebApiContext
  extensions:
    Behat\WebApiExtension\ServiceContainer\WebApiExtension:
      base_url: 'http://127.0.0.1:8001'

Then a single scenario against an existing route:

Feature: Users API
  Scenario: Fetch an existing user
    When I send a GET request to "/api/users/1"
    Then the response status code should be 200
    And the response should be JSON

Run vendor/bin/behat --no-colors. The decision gate: if getting this one scenario green requires you to debug the extension, its HTTP client, or the Composer graph, abandon the trial. A custom Behat context wrapping Guzzle is roughly 60 lines and you own it; a maintained alternative is better still.

Implementation Patterns

Keep the extension as a transport layer only. The single most important pattern: let it send requests, and put every domain assertion in your own context. This makes the eventual migration a mechanical port rather than a rewrite.

Subclass the bundled context, register only the subclass. Registering both WebApiContext and your subclass produces ambiguous-step failures. Put auth, fixture setup, and domain steps in a child class:

namespace Tests\Behat;

use Behat\WebApiExtension\Context\WebApiContext;
use Behat\Behat\Hook\Scope\BeforeScenarioScope;

class ApiContext extends WebApiContext
{
    /** @BeforeScenario */
    public function authenticate(BeforeScenarioScope $scope): void
    {
        // Obtain a token via Laravel's own test users, then attach it
        // to the underlying HTTP client/request builder exposed by the parent.
    }
}

Check the parent's actual property/method names in src/ — the extension is undocumented enough that you should not guess an accessor.

Drive the app from a real HTTP boundary, not the test harness. Because this is black-box, Laravel-specific test affordances don't apply: no RefreshDatabase (that trait lives inside the PHPUnit/Illuminate\Foundation\Testing harness), no facade fakes, no container swaps, no actingAs(). Instead:

  • Seed state through an artisan command you expose for tests (php artisan migrate:fresh --seed --env=testing), invoked in a @BeforeSuite shell step or your CI script.
  • Use a dedicated test database and .env.testing so the Behat process and the serving process agree on state.
  • For auth, mint a token with a small artisan command rather than scripting a login flow through the API.

Environment-specific base URLs, not committed secrets. Behat 3 predates reliable env interpolation in behat.yml. The robust pattern is a CI step that templates behat.yml from environment variables, or a per-environment profile:

ci:
  extensions:
    Behat\WebApiExtension\ServiceContainer\WebApiExtension:
      base_url: 'http://api:8000'

Run with vendor/bin/behat --profile=ci, and keep the profile file out of version control.

Scenario Outlines for contract matrices. This is where BDD genuinely pays for API work — status code and response-shape permutations as tables rather than duplicated scenarios. It also keeps your specification value intact if you later re-home these cases into Pest datasets.

Chain deeper payload validation in a custom step. Do not expect the extension to do JSON Schema validation. Add justinrainbow/json-schema (or similar) and write one step that validates the last response body against a schema file. The extension's job ends at "here is the JSON."

Put the exit plan in the repo. A short docs/adr/ note recording why an abandoned dependency is present, who owns it, and the target replacement. This is the difference between a controlled legacy decision and a slow-motion incident.

Gotchas and Tips

  • Abandoned, never stable, zero dependents. No security patches, no PHP 8.x compatibility guarantees, no Behat 4 path, no community pressure to fix anything. If you keep it, fork it into your VCS or vendor the source so a future composer change can't silently remove it.

  • Symfony constraint collisions are the practical blocker in Laravel. Laravel pins Symfony components for Console, HttpFoundation, Finder, and more. A Behat 3 graph will want older ones. Pin the entire graph — laravel/framework included — and run composer update --dry-run before any routine dependency bump. If a routine Laravel minor upgrade breaks the test suite, you have the wrong tool.

  • Abandonment warnings in output. composer install prints an abandoned-package notice. Filter it deliberately in CI logs; don't blanket-suppress composer warnings or you'll miss real advisories.

  • Config key ambiguity. Behat 3 extensions are addressable by FQCN or short alias. If behat.yml fails validation, check getConfigKey() on the extension class and mirror it exactly, and verify the indentation under extensions: — a misnested block silently produces a "no extension found" error that looks like a config key problem.

  • Trailing-slash joining bugs. Old HTTP wrappers concatenate naively. Test base_url with and without a trailing slash, and paths with and without a leading /. /api/users against http://host/api can produce http://host/api/api/users or //api/users.

  • TLS, proxies, and self-signed local certs. If the underlying client verifies strictly, a Sail/mkcert local stack fails with an opaque connection error. Find the client config option or override the client service via behat.yml's services section — do not disable verification globally.

  • Debugging failing steps. vendor/bin/behat -vvv --no-colors, plus a temporary @AfterScenario hook that dumps the last response status, headers, and body. With documentation this thin, the response dump is the fastest way to learn what the extension actually sent.

  • Ambiguous steps from double registration. One context per suite. If you see "step is ambiguous," you registered both the parent and your subclass.

  • Limited extension points. You get service overrides via Behat's container and context subclassing — no plugin hooks, no PSR-18 client injection, no modern middleware story. Design custom contexts so their internals are swappable.

  • Keep Gherkin portable. Avoid extension-specific phrasing where a plain-English step of your own would do. When you migrate (likely, and soon), feature files should survive unchanged and only the context implementation should need rewriting.

  • The Laravel-native alternative is the yardstick. Before extending this package, write the same scenario in Pest:

    it('returns an existing user', function () {
        $this->getJson('/api/users/1')
            ->assertOk()
            ->assertJsonPath('id', 1);
    });
    

    If that covers your need — and for a Laravel API it usually does — the abandoned Behat extension buys you nothing but a maintenance liability.

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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource