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

Phpunit Service Create Trait Laravel Package

pkly/phpunit-service-create-trait

A PHPUnit 12+ trait (PHP 8.4+) that builds real service instances with auto-mocked constructor and #[Required] dependencies. Dependencies stay as invisible stubs until fetched, so you only get mock verification where you ask for it.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Architecture fit. The package solves a real and recurring problem in Laravel service-layer unit tests: constructor signatures churn, and every churn ripples into setUp() boilerplate across dozens of test classes. A single-call factory that fills every dependency with a test double removes that coupling and makes test setup order-independent, which is a genuine ergonomic win for services with 4+ injected dependencies (repositories, HTTP clients, queues, loggers, clocks).

The fit is also narrower than the pitch implies. The trait constructs services with new and hand-assembled doubles — it deliberately bypasses the Laravel container. That means container bindings, contextual resolution, #[Inject], decorators, config-driven factories, and service-provider aliasing are all absent from the object under test. For a pure domain service with interface-typed dependencies this is exactly right. For anything that depends on container semantics, the test verifies a wiring that does not exist in production. Additionally, the #[Required] method-injection support is Symfony's autowiring attribute; Laravel has no direct analog, so for a Laravel codebase that half of the feature set is inert. The package is framework-agnostic PHPUnit tooling, not Laravel tooling, and should be evaluated as such.

Integration feasibility. High on a per-test basis: the trait is additive, requires no base-class change, no bootstrap change, and no production code change. It composes with Tests\TestCase, RefreshDatabase, data providers, and Laravel's existing createMock() usage — a team can adopt it in one test class and ignore it elsewhere. PHPUnit's own createMock/createStub do the heavy lifting, so behavior stays familiar. Exit cost is near zero: reverting a test means replacing one line with explicit constructor calls.

The hard gate is version support: PHP 8.4+ and PHPUnit 12/13 only. Laravel 11/12 projects default to PHPUnit 10/11 and commonly run PHP 8.2/8.3. As of today most target teams cannot install this without upgrading either PHPUnit or PHP first, which makes adoption an infrastructure decision rather than a testing-tooling decision. That is the single largest constraint on feasibility and should be scoped before any pilot.

Technical risk. The dominant risk is false confidence from container divergence (described above), not crashes. Secondary risks: unmockable types — final classes, enums, scalars without defaults, variadics, union/intersection types, self/parent hints, and some internal PHP classes — surface as setUp() failures and force per-parameter overrides, eroding the boilerplate savings; auto-wiring also masks the friction that would otherwise signal "this constructor now has nine dependencies," so architectural drift becomes quieter; and the trait's internals reach into PHPUnit's mock registry, so PHPUnit major or minor releases are a plausible breakage vector with no community buffer to catch it. Supply-chain exposure is capped: require-dev only, MIT, small surface, no runtime presence in production artifacts.

Maturity signals are the counterweight: 0 stars, 0 dependents, a single maintainer, a handful of releases, README-only documentation. The score of ~20.8 is an adoption/distribution signal rather than a code-quality verdict — the code is small enough to audit in an afternoon and to fork in a day. A TPM should treat this as "credible pattern, unproven dependency."

Key questions.

  • Does the trait respect container bindings anywhere, or is new unconditional? Can a service be constructed via app()->make() with selective overrides instead?
  • How does it behave with final classes, enums, variadics, union/intersection types, and readonly promoted properties — clean error or opaque failure?
  • Are the PHPUnit 12/13 and PHP 8.4 floors hard requirements or incidental? Would a maintained fork support PHPUnit 11?
  • Does it work under Pest, and with Laravel's Tests\TestCase (full app boot) rather than plain PHPUnit\Framework\TestCase?
  • What do return types look like to PHPStan/Larastan — does getMockedService() carry generics, or will it degrade static-analysis levels?
  • Is there a supported path if the maintainer stops releasing, and are we willing to vendor under MIT with attribution?
  • Does the team's codebase actually have container-coupled services that this would misrepresent?

Integration Approach

Stack fit. Best fit: PHP 8.4+, PHPUnit 12/13, Laravel service classes with interface-typed constructor dependencies and no container-dependent behavior. Poor fit: Pest-first teams already served by mock()/fake() helpers, codebases on PHPUnit 10/11, services resolving dependencies through facades or the container at runtime, and integration/HTTP/E2E suites where Laravel's own helpers are authoritative. The trait should be positioned as a unit-test convenience for one identified layer, not a test-strategy standard.

Migration path. None required and none possible in bulk — adoption is per test class and reversible per test class. Practical path: vendor or install as a dev dependency, add an internal convention note, convert one test class by hand to establish the pattern, then convert opportunistically as tests are touched. Do not run a mass codemod; the value is concentrated in high-dependency-count service tests, and a broad sweep maximizes exposure to the unmockable-type edge cases before the team has learned them.

Compatibility. Verify in this order: PHP runtime version, PHPUnit version, Laravel test base class, Pest presence, static-analysis level, CI PHP matrix. Each is a blocker-or-not gate. Also confirm the package's transitive dev requirements do not conflict with the project's pinned PHPUnit constraints, since a dev dependency conflict here can force a wider lockfile change than intended.

Sequencing.

  1. Gate (1 day): confirm PHP 8.4 + PHPUnit 12/13 availability on the target branch; if blocked, park as "watch" and revisit at the next framework upgrade rather than upgrading PHPUnit for this package's sake.
  2. Audit (1 day): read the trait source; confirm failure behavior on final classes, enums, and scalars; decide fork-vs-consume.
  3. Spike (2–3 days): convert 2–3 representative service test classes; record setup-line delta, runtime delta, and every edge case hit.
  4. Pilot (2 weeks): one squad, one bounded module (e.g., order or payment service tests), with a written convention covering when to fetch a mock, when to pass explicit parameters, and how to disambiguate same-type dependencies by parameter name.
  5. Decision gate: adopt, adopt-with-vendored-fork, or reject — measured against the metrics below, not against enthusiasm.
  6. Rollout (if adopted): opportunistic conversion, one internal pattern doc, and a lint rule or review checklist limiting it to require-dev usage.

Success metrics worth instrumenting: setup lines per service test, time-to-first-green-test for new engineers, unit-suite runtime, flake rate, and — most importantly — defect-escape rate on service-layer changes, to confirm the tests still catch regressions rather than merely passing.

Operational Impact

Maintenance. The dependency itself is close to maintenance-free, but it is unowned upstream in any practical sense: one maintainer, no visible contributor base, no dependents to surface regressions before we do. A TPM should pre-commit to either (a) pinning an exact version with a documented fork procedure, or (b) vendoring the trait into the repo as internal test tooling on day one. Given the size, vendoring is the lower-variance option and removes the PHPUnit-version churn question from the release calendar entirely.

Support. No vendor support, no SLA, README-only documentation, no example repository beyond the README snippets. Internal support burden is entirely ours: a named owner for the trait, a short internal README covering the stub-vs-mock registration model, same-type disambiguation, and the explicit-override escape hatch, plus a known-issues list for unmockable types. Budget roughly half a day of ramp-up per engineer and one brown-bag session; the non-obvious parts are that fetching a double is what registers it with PHPUnit, and that doubles never fetched are intentionally invisible.

Scaling. Constructing real instances immediately is cheap, so per-test overhead is negligible even at thousands of tests. The scaling risk is behavioral, not computational: uniform auto-wiring encourages mocks for every dependency by default, which inflates mock surface area and produces tests that assert orchestration of fakes rather than behavior. Left unmanaged at suite scale this raises the cost of refactoring production code, which is the opposite of the intent. Pair adoption with a review norm: dependencies fetched only when their behavior matters, real collaborators used where they are cheap.

Failure modes.

  • Wiring divergence — tests pass against a hand-built graph that the container would never produce. Mitigation: reserve the trait for services with no container-dependent behavior; keep at least one container-level test per service.
  • Silent signature drift — the trait hides added dependencies, so design erosion becomes invisible. Mitigation: flag constructor argument count thresholds in architecture review.
  • Unmockable types — final classes, enums, scalars, variadics fail at setUp(); every occurrence costs an explicit override and a confused engineer.
  • PHPUnit upgrade breakage — internals coupled to the mock registry; a major upgrade can break the trait across the suite at once.
  • Abandonment — no security exposure (dev-only), but a hard block on PHPUnit upgrades until we fork.
  • Over-mocking — green suite, weak regression signal; surfaced only by defect-escape metrics.

Ramp-up. Low ceiling, moderate friction. The API is one constructor call plus two accessors, but the mental model — stubs by default, mocks on demand, registration-by-fetch, order independence, parameter-name disambiguation for duplicate types — is idiosyncratic relative to vanilla PHPUnit and must be taught rather than discovered. Two artifacts make it cheap: a five-line canonical example in the repo's testing guide, and an explicit "when not to use this" list. Without them, teams will either under-use it (missing the boilerplate savings) or over-use it (mocking everything, including things they should let run).

Recommendation. Treat as a validated pattern with an unvalidated dependency. Do not adopt org-wide on the strength of the concept; run the version gate and a two-week, one-squad pilot, and prefer a vendored copy pinned to the current PHPUnit line over an unowned Packagist dependency in the critical test path.

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.
andydefer/laravel-notification
adachsoft/cli-process-tester
jhaskell13/laraflow
sebastienheyd/boilerplate-media-manager
ccast/tagixo-primix
pretendtrue/laravel-builder
sawmainek/laravel-scout-tntsearch-driver
tomshaw/laravel-calendar-table
dibakar/laravel-ownership
steadrun/laravel-monitor
labyrinthe/connect-laravel
elmogy/layan
drewdan/sentinel-client
dskripchenko/laravel-llm
fatryst/nuonuo
stevenay/myanfont
gmostafa/php-graphql-client
xeviant/paystack
snowsoft/faker
meditationsteps/blogetc