spiral/testing
Spiral Testing SDK provides a TestCase and custom TestApp implementation for testing Spiral Framework applications and packages. Configure bootloaders and root directory to run tests without building extra app classes. Requires PHP 8.1+ and Spiral 3.15+.
Framing correction (material to the decision): spiral/testing is not a Laravel/PHP package in the Laravel sense — it is the official test SDK for the Spiral Framework, a RoadRunner-first, long-running-worker PHP framework. It has no Laravel service provider, no Illuminate\Foundation\Testing integration, and no compatibility with Laravel's TestCase, RefreshDatabase, HTTP kernel, or container. The brief's "Laravel/PHP package" label is a taxonomy artifact (it is a Composer package written in PHP). Any evaluation must therefore answer one question first: are we, or will we be, running Spiral? If no, this package is out of scope and the correct disposition is "no action."
Architecture fit (conditional on Spiral adoption): Strong. The SDK is purpose-built for Spiral's architecture, which differs from Laravel's in ways that make off-the-shelf PHPUnit harnesses inadequate:
#[TestScope] attribute). This is exactly the class of bug a hand-rolled harness gets wrong.defineBootloaders() + rootDirectory() let a package be tested without a full application skeleton — the core value proposition in the README.TestableKernelInterface return from getApp() (2.3.0) gives typed access to kernel internals for integration-level assertions.FakeHttp (with withMiddleware/withoutMiddleware, stream bodies, JSON helpers), FakeEventDispatcher (Laravel-shaped assertion vocabulary: assertDispatched, assertListening, assertNothingDispatched), queue payloads, console/scaffolder command assertions, config fragment assertions, and container binding assertions.Integration feasibility: Low effort if Spiral is already the stack — it is a dev-only dependency added to require-dev, extended via a single abstract TestCase. High effort otherwise: adopting it implies adopting Spiral's kernel, container, bootloader, and RoadRunner bridge (2.3.0 added roadrunner-bridge 2.x/3.x support). There is no bridge, shim, or adapter to Laravel, Symfony's KernelTestCase, or Pest's Laravel plugin.
Technical risk:
| Risk | Severity | Notes |
|---|---|---|
| Ecosystem mismatch (Laravel org) | Blocking | Wrong framework entirely; zero reuse. |
| Framework version coupling | Medium | Hard floor of Spiral 3.15+ (2.9.0 middleware fakes); 2.7.0 was required for 3.12 dispatchers. SDK and framework must be upgraded in lockstep. |
| PHPUnit major coupling | Medium | 3.0.0 bumps to PHPUnit 13.0.5 as a major release; 2.9.1 existed solely to resolve a PHPUnit 10.5.46 conflict. Test-runner upgrades become coordinated change events. |
| Low external adoption signal | Medium | 7 stars, 0 dependents. Bug discovery is effectively first-party only. Mitigant: it is maintained by identifiable Spiral core maintainers (roxblnfk, msmakouz, butschster), and the changelog shows continuous, small, purposeful fixes rather than abandonment. |
| Metadata integrity | Low | The stated last-release date (2026-10-04) does not reconcile with the changelog ordering (3.1.2 latest). Verify against Packagist before pinning — do not let a bad timestamp drive a version decision. |
| Test isolation / container leakage | Low–Medium | Historically real (2.4.1 singleton rebinding fix, 2.3.1 FilesInterface rebinding restore, 2.12.2 proxy-scope exceptions). Acceptable at current maturity, but warrants a smoke-test of our own long-running-state scenarios. |
Key questions for the maintainer/team:
Stack fit: Binary, not graded. Fit is ~excellent on Spiral 3.15+/PHP 8.1+, and zero on Laravel. There is no meaningful partial adoption: the SDK's TestCase replaces the project's base test class, and its fakes replace DI-level substitutions. Anything less is cherry-picking assertion helpers that are already trivial to write.
If the stack is Laravel (the likely reading of the brief): recommend decline / no integration. Laravel's first-party Illuminate\Foundation\Testing already provides equivalent capability — $this->get()/postJson(), Http::fake(), Event::fake() with assertDispatched/assertListening, Bus::fake(), Queue::fake(), assertDatabaseHas, and Pest support. Duplicating that with a Spiral-only SDK is technically impossible and organizationally pointless. The only transferable artifact is API vocabulary (the assertion naming parity is deliberate), which can inform an internal testing style guide at zero cost.
If the stack is Spiral: the migration path is narrow and cheap.
composer require --dev spiral/testing (dev-only; do not leak into runtime).TestCase per repository; implement rootDirectory() and defineBootloaders() with the minimal bootloader set the package under test actually requires — resist pulling the full app bootloader list, as that reintroduces the coupling the package exists to avoid.fakeHttp()), then events (fakeEventDispatcher()), then console/scaffolder assertions.#[TestScope] for tests that need an isolated container scope rather than hand-rolled setUp/tearDown rebinding — this is the highest-leverage, lowest-risk adoption.Compatibility and sequencing: Because 3.0.0 is a PHPUnit-major bump, sequence as: (a) confirm Spiral framework ≥3.15 and RoadRunner bridge in the support window; (b) choose SDK major to match our existing PHPUnit major — do not upgrade PHPUnit and the SDK in the same change set; (c) adopt the SDK on 2.x if we are on PHPUnit ≤12, and defer 3.x until a deliberate PHPUnit 13 migration; (d) after the base TestCase is proven, migrate fakes file-by-file, keeping the old harness in place behind a flag until parity is demonstrated.
Vendor/lock-in posture: MIT and first-party, so legal and provenance risk are minimal; the real lock-in is framework-level (Spiral), not package-level. That decision has already been made or not made before this package enters the conversation.
Maintenance: Low direct burden and low churn — the changelog is a steady stream of small, targeted patches (assertion messages, line-ending tolerance in output assertions, Symfony Mime v7 allowance, PHP 8.4 compatibility, static analysis added in 2.8.3). No sign of a stalled project. However, maintenance cost is not zero for us: every PHPUnit major and every Spiral minor that touches dispatchers or middleware can force a coordinated upgrade of the test harness (2.7.0 and 2.9.0 are exactly that). Budget roughly one planned harness-upgrade task per framework/PHPUnit major cycle, plus a recurring triage of flaky-scope issues.
Support: Effectively first-party Spiral support via GitHub issues and the official docs page (spiral.dev/docs/testing-start), with no commercial SLA and no meaningful third-party ecosystem to fall back on. With 0 dependents, we will not benefit from someone else hitting our bug first. For a Laravel organization, "support" is a non-question — the package does not apply. Practical implication for any adopting team: assign explicit internal ownership of the base TestCase and treat it as load-bearing test infrastructure, not a dependency we merely consume.
Scaling: Scales well on its intended axis: more packages and more test cases need nothing beyond additional test classes; rootDirectory()/defineBootloaders() compose per package. Costs do not grow with suite size in a way unique to this SDK. The scaling limit is framework-version sprawl — multiple services on different Spiral minors each need a compatible SDK major, so a monorepo-per-service split multiplies the upgrade matrix. Keep to one SDK major across the estate.
Failure modes to design against:
FilesInterface binding restore, 2.12.2 finalizer exception handling) show it is a live failure class. Mitigation: always go through fakeHttp() rather than invoking kernel internals directly, and assert isolation explicitly in a couple of canary tests.assertStatus failure; older pins give uninformative failures that teams learn to ignore. Mitigation: pin to a recent patch within the chosen major.symfony/mime constraint can block upgrades (precedent: 2.9.1). Mitigation: CI matrix + Renovate/Dependabot on the dev dependency group.Ramp-up: For a PHP engineer already fluent in PHPUnit and Laravel-style testing, this is roughly half a day — the assertion vocabulary is deliberately familiar, and the only genuinely new concepts are bootloader selection and scope. For a team with no Spiral exposure, ramp-up is dominated by Spiral itself (DI container, bootloaders, RoadRunner worker lifecycle, scopes), realistically 1–2 weeks before the SDK's value is even legible. Documentation is thin — one README plus an external docs page — so expect tribal knowledge to accrete around the internal base TestCase; capture it there and in a short internal guide, or it will be relearned by each new hire.
How can I help you explore Laravel packages today?