dvdoug/behat-code-coverage
Behat Code Coverage is a Behat extension that generates PHP code coverage reports while running your test suite. It wraps php-code-coverage—the same library used by PHPUnit—so reports are familiar and interoperable with existing tooling.
What the package actually is. dvdoug/behat-code-coverage is a Behat extension — not a Laravel package. It registers a Behat service container extension that wraps phpunit/php-code-coverage around a Behat run, then emits reports in the formats developers already consume (Clover, Cobertura, OpenClover, HTML, text, serialized). It does not instrument anything itself; it is a thin, well-maintained adapter between Behat's lifecycle and the same coverage engine PHPUnit uses.
Architecture fit. Fit is entirely a function of whether the team runs Behat at all. Laravel's default and idiomatic test stack is PHPUnit/Pest, where coverage is native (php artisan test --coverage, --coverage-clover). If the team has no Behat suite, this package has zero value and should be rejected on the first pass. It becomes relevant only in the narrower case where Behat/Gherkin acceptance tests exist against a Laravel app (via laracasts/behat-laravel-extension or bespoke contexts) and stakeholders want a single coverage artifact spanning both unit and acceptance layers.
The critical technical risk: process locality. Coverage is collected in-process. Behat scenarios must exercise application code inside the same PHP process that started the Behat run. If the suite drives a separately booted php artisan serve/nginx/php-fpm instance over HTTP — the classic Laravel acceptance setup — instrumented coverage will be near-empty and misleading. The historical RemoteXDebug driver that addressed this was removed in 5.0.0. This is not a defect, but it forces an architectural decision (in-process kernel bootstrapping, e.g. Laravel's testing kernel invoked from a Behat context) before the package is worth adopting. Verify this before anything else.
Dependency coupling. The package's major-version cadence is pinned to phpunit/php-code-coverage majors (v14 supported per 5.4.2, v13 in 5.4.1, v12 in 5.3.7, v11 in 5.3.3, etc.). php-code-coverage is normally a transitive dev dependency of PHPUnit, so the practical failure mode is a Composer resolution conflict: the repo's PHPUnit pins one php-code-coverage major while the extension demands another. This is the single most likely integration blocker and is invisible until composer update runs. Symfony component support is 6 and 7 (Symfony 4 removed in 5.3.2); Laravel 8's Symfony 5.x floor therefore falls outside the supported matrix and would require an older, unsupported extension release.
Maturity and sustainability. Signals are mixed but net-positive on engineering hygiene and negative on adoption. Positives: conventional-commit changelog with explicit compatibility notes, GitHub Actions CI badge, Read the Docs site, BSD-2-Clause (permissive, low legal friction), and a demonstrated pattern of shipping upstream-compatibility releases quickly. Negatives: 62 stars, zero listed dependents, effectively a single-maintainer project, and a composite score of ~22. Bus factor is 1. For a dev-only tooling dependency that is acceptable; for anything load-bearing it is not. Treat it as a replaceable component with a documented exit (drop the extension, revert to PHPUnit-native coverage).
Key questions.
phpunit/php-code-coverage major does our PHPUnit currently resolve, and does it intersect this package's constraint?Stack fit. Composer-only, dev dependency, configured entirely through a behat.yml extension block (reports, filter, cache, branchAndPathCoverage). No application code changes, no service provider, no config publishing. That is an unusually clean integration surface: the blast radius is limited to the Behat profile file and composer.json. It sits comfortably alongside an existing PHPUnit coverage pipeline because both emit the same artifact types from the same engine — the outputs are directly comparable and mergeable in downstream tooling.
Migration path. Greenfield, not migration. Suggested sequence:
require-dev, run composer update --dry-run first to surface any php-code-coverage constraint conflict before touching the lockfile.filter scoped to app/ and explicit exclusions for vendor/, database/migrations/, and framework bootstrap files.Compatibility and sequencing. Do the dependency resolution check as a timeboxed spike before any pipeline work; if it conflicts, fall back to running the extension in an isolated Composer profile or a separate CI job with its own lockfile rather than forcing a resolution in the main tree. Sequence the rollout behind the existing gates: land the Behat coverage job as non-blocking and reporting-only, let it accumulate a baseline for a sprint, then decide on thresholds. Do not introduce a gate in the same change that introduces the tool — you will not know whether a red build means a regression or a misconfiguration.
Tooling integration. Clover output is broadly consumable: SonarQube, Codecov, GitLab's coverage: regex, Jenkins, and most PR-bot coverage reporters accept it. Prefer Clover as the canonical format and add HTML only as a developer convenience artifact, since HTML reports are large and not diffable. Note that the package generates reports but does not enforce thresholds; if a gate is required, it must come from a downstream parser or the platform's native coverage policy.
Maintenance. Low ongoing effort, bounded risk. The extension has no application-side code, so upgrades are Composer bumps plus a config review. The real maintenance cost is tracking the php-code-coverage/PHPUnit version treadmill: this package ships compatibility releases on the upstream major cadence, which means a PHPUnit major upgrade now has a second-order dependency to sequence. Budget a small but recurring allowance for that, and pin deliberately rather than floating.
Support and bus factor. Single maintainer, small user base, public issue tracker. Response quality is undocumented here; assume best-effort. Because the package is dev-only tooling, an abandonment scenario degrades gracefully — the fallback is deleting the extension and reverting to PHPUnit-native coverage, or forking (BSD-2-Clause permits it). This should be recorded as an explicit accepted risk rather than left implicit.
Scaling. Two cost axes. First, wall-clock: coverage instrumentation is expensive, and Behat suites are already the slowest layer; Xdebug-driven coverage can multiply runtime several-fold, which on a large Gherkin suite is the difference between a usable PR job and one that gets disabled. Mitigate with PCOV (substantially cheaper than Xdebug for line coverage, at the cost of branch/path coverage), scoping filter tightly, and enabling the static analysis cache key (defaults to the system temp dir — override to a CI-cacheable path to avoid re-parsing the codebase every run). Second, concurrency: the cache directory is shared state; parallelized Behat runs need per-worker cache paths or they will contend.
Failure modes and how they present.
filter that includes vendor — a configuration error masquerading as a quality signal, and the most damaging failure because it looks like data.Ramp-up. Genuinely small for anyone already fluent in Behat profiles and Composer: an afternoon to a first report, a day or two to a stable CI job. The hidden cost is not the package — it is the prerequisite fluency in Behat's extension/container model and in coverage-driver operations, plus the architectural work of making the acceptance suite run in-process. Teams without a Behat suite should not adopt this to acquire one. Teams with a Behat suite that drives a real web server should expect the majority of the effort to land on restructuring the test harness, not on the extension.
How can I help you explore Laravel packages today?