macpaw/behat-nelmio-describer
A Behat extension that hooks into NelmioApiDocBundle to automatically describe and validate JSON responses in your scenarios. Instead of hand-writing assertions, it compares API output against your existing Nelmio documentation, keeping tests concise and your docs honest.
This is a small Symfony bundle that bridges two tools you probably already run: Behat (which exercises your API for real) and NelmioApiDocBundle (which renders /api/doc). Instead of hand-writing example payloads in PHP annotations/attributes, it captures the actual response bodies from your Behat scenarios and injects them into the generated OpenAPI/Swagger UI as sample responses.
Update in v2.0.1 (Sept 2024): the package gained Symfony 7.0 support. That's the only change in the release — no new features, no API/behaviour changes, so it's a drop-in bump from 2.0.0. Practically it means the bundle is no longer pinned out of modern Symfony stacks, and if you were previously excluded by the symfony/* constraint this is the version to try. It also means the "archived, dead package" framing from earlier reviews needs re-checking — confirm the current repo state on GitHub before committing either way, but a maintainer-followed release with a Symfony 7 PR is a better signal than the last-release date alone.
Before you invest time: adoption is still tiny (single-digit stars, effectively no dependents). Treat it as a thin pattern you can vendor or fork, not a dependency with a long-term support guarantee. Version drift is still the most likely reason it won't install — check composer.json against your Nelmio and Behat versions, not just Symfony.
Minimal steps:
composer require --dev macpaw/behat-nelmio-describer:^2.0.1config/bundles.php (or let Flex do it if the recipe exists). If there's no recipe, add it manually — Symfony 5.3+ won't autodiscover an unregistered bundle.src/ and find the Behat extension class (look for something implementing Behat's ExtensionInterface or a class named Extension). That class tells you the exact config key names. Register it in behat.yml:default:
extensions:
MacPaw\BehatNelmioDescriber\Extension: ~ # verify namespace/name in src/
Resources/config/services.xml — this is where you'll see how captured responses are stored (almost certainly a directory or cache pool) and how the Nelmio describer consumes them.First use case to validate end-to-end: pick your simplest GET endpoint, tag its scenario the way the README/bundle expects, run vendor/bin/behat, then load /api/doc and confirm the response example changed. If it did, the whole pipeline works and you can expand from there.
Scenario tagging as the switch. The core workflow is: tag a Behat scenario, run it, and the response gets persisted as an example for the matching route. Tag only the scenarios you want surfaced as documentation — mixing in error-path scenarios will give you 422 example bodies on endpoints you want to look happy-path in the UI.
Where the capture happens. The package has to read the response out of Behat's HTTP context after the request completes. If the bundle ships a context or hook, use it verbatim; if not, write a @AfterScenario hook that pulls $this->getSession()->getResponse()/the response from whatever Mink/HTTP-client context you use and hands it to the bundle's storage service. That service is your integration point — inject it by ID rather than instantiating anything.
Keep the run order in CI predictable. Documentation examples are only as fresh as the last Behat run. In a pipeline, do behat → (optionally warm the cache) → export/deploy the doc. If your doc build runs in a separate container from the test job, the store directory has to be shared as an artifact, or you'll silently ship last week's examples.
Pair with Nelmio's native examples for the gaps. Real captured responses are great for the "this is what it actually returns" story, but Behat won't hit every documented response code. Use Nelmio's own examples/responses config for codes Behat never exercises (500s, 401s), and let the describer fill in the rest.
One scenario per representative example. If you need multiple examples per endpoint, prefer separate tagged scenarios over trying to accumulate several captures — file-based stores typically key by route and the last write wins, or you get ordering surprises.
2.0.0, but also no sign of a hardening pass. Pin the exact version in composer.json and be prepared to fork. The two things most likely to break next: Nelmio's internal describer interfaces (they changed across v3→v4→v5) and Behat's extension/container APIs. Symfony 7 support implies a PHP 8.2+ floor — check that against your CI matrix before bumping.localhost:8000 while Nelmio documents the canonical host. If examples don't attach, the key derivation is almost certainly the culprit — check how the bundle maps a response to an operationId/path and normalize accordingly.Content-Type in your Behat context before handing the body off.behat -vvv, verify the tag is recognized (Behat warns on unknown tags in newer versions), then check the store directory for a file after the run. If the file exists but the doc doesn't change, the problem is on the Nelmio side (describer not registered, cache not cleared) — bin/console cache:clear and check that the bundle's compiler pass actually ran. On Symfony 7, double-check the service definitions actually load under the new autowiring defaults — a compatibility bump that "installs fine" can still register services with the wrong visibility.Bottom line: the idea — generate API docs from tests that actually pass — is genuinely good and worth implementing. v2.0.1 keeps the package installable on modern Symfony (7.0), which removes the main practical blocker, but it is still a thin, low-activity implementation. Budget a day to read src/ and be ready to own it.
How can I help you explore Laravel packages today?