spatie/pest-plugin-test-time
A Pest plugin for controlling time in tests. Freeze or advance Carbon’s current time with testTime()->freeze() and addMinute(), and use the toBeCarbon expectation to assert Carbon date and time values clearly.
Architecture fit. This is a test-only dev dependency with no production surface: no service provider, no config, no facade, no published assets, no migrations. It is a Composer plugin that Pest auto-discovers, and it is a thin wrapper over Carbon::setTestNow(). Architecturally it touches exactly one boundary — the test harness — and nothing else. Fit is clean but narrow: it assumes Laravel-family conventions (Carbon as the time source) and assumes Pest. In a PHPUnit suite, the package delivers zero value.
Integration feasibility. This is close to the lowest-effort integration a TPM will ever sponsor: one composer require --dev line, no CI pipeline changes beyond the existing Pest run, no environment configuration, no secrets, no infra. Realistic effort is 1–2 hours to pilot on one suite and write a short house-style note. There is no meaningful proof-of-concept phase required; the risk is not "will it install" but "will the team use it consistently."
Technical risk. Four risks matter, ranked:
setTestNow() mutates process-wide clock state. Without a defensive afterEach(fn () => Carbon::setTestNow()), one frozen test leaks into the next. The signature symptom is the worst kind of failure for a PM to reason about: a test passes in isolation and fails in the full suite, intermittently, depending on ordering.time(), mktime(), SQL NOW()/CURRENT_TIMESTAMP, or Carbon::createFromTimestamp(time()). Any domain logic or raw query reading the native clock or the database will silently diverge from the frozen value. The test goes green while asserting something the system does not actually do — a correctness risk wearing the costume of a quality improvement.testTime() plus one expectation, so the exit cost is a find-and-replace back to Carbon::setTestNow(). This is vendorable-in-an-afternoon risk, not platform risk.Competitive reality to weigh. If the codebase is on Laravel, the framework already ships freezeTime(), travelTo(), travel()->days(), and travelBack(). The marginal value of this package over what you already own is therefore (a) terser Pest-closure ergonomics and (b) the toBeCarbon expectation, which is genuinely the most durable differentiator for review readability. A TPM should frame adoption as an ergonomics decision, not a capability decision.
Key questions to resolve before committing:
now(), or do raw SQL NOW() and native time() appear in date-sensitive paths?travel* helpers? Standardizing on a second idiom is a net negative.Stack fit. Laravel (or any PHP project using Carbon) + Pest. Works with CarbonImmutable. Does not apply to PHPUnit-only suites, non-Carbon date libraries, or teams without a Pest migration on the roadmap. If the org is mid-migration from PHPUnit to Pest, this package is correctly sequenced after that migration completes, not before.
Migration path. None required — the integration is purely additive. No existing tests change behavior. The exit path is equally clean: remove the dependency and replace testTime()->freeze(...) / add* / sub* calls with direct Carbon::setTestNow() invocations, and inline the toBeCarbon assertions. Documenting that exit path up front is worth ten minutes of a TPM's time because it converts "new dependency" into "reversible tooling choice," which changes how risk-averse reviewers receive it.
Compatibility. Verify and record three coordinates before rollout: Pest major, PHP floor, Carbon major. Treat the version pin as a deliberate decision in the PR, not an incidental one. If mixed Pest versions exist across repositories, expect the pin to differ per repo and standardize the convention, not the version number.
Sequencing.
afterEach reset in Pest.php in the same PR as the first use. Do not defer this.Pest.php for recurring domain moments (renewal window, month-end, leap day) so the concept has one definition.now() inside the assertion, always pass an explicit format to toBeCarbon in shared code.Maintenance. Effectively nil at runtime — there is no production artifact, so no on-call exposure, no perf surface, no security patch urgency beyond routine dependency hygiene. The real maintenance events are annual-ish: a Pest major bump, a PHP floor bump, a Carbon major bump. Renovate/Dependabot will handle routine version churn; the Pest 5 / PHP 8.4 / Carbon 3 transition is the one that will require a deliberate ticket because it forces an infrastructure decision the package cannot make for you.
Support. MIT, community-maintained Spatie OSS. No SLA, no paid support tier for this package, no dependents to indicate ecosystem pressure to fix issues. Internal ownership should sit with whoever owns test infrastructure — not distributed across application teams, who will each invent a slightly different reset pattern.
Scaling. Technically the package scales without limit. The scaling problem is convention scaling: N teams, N flavours of freeze/reset/assert. Left unmanaged you get suites that are individually readable and collectively inconsistent, plus occasional cross-file flakiness that erodes trust in the test suite as a whole. The mitigation is cheap (one helper file, one paragraph of documented style) but it must be owned by someone.
Failure modes.
afterEach reset and randomized-order CI.NOW() or native time() diverges from frozen Carbon; tests pass while behavior is unverified. Highest-severity failure, because it is invisible.travel() and testTime() used interchangeably across repos, doubling the cognitive load for anyone moving between codebases.Ramp-up. Under an hour for a competent Laravel developer. The learning is not the API — it is the setTestNow() mental model and its limits (what it does not freeze). Budget the ramp-up time on the limits, not the syntax. That is where the correctness risk lives, and it is the only part of onboarding that a TPM should insist is documented.
Verdict. Recommend as an optional, opt-in convenience for Pest-based Laravel repositories, with adoption concentrated on date-heavy domains where the ergonomics and toBeCarbon readability pay for themselves. Do not mandate it org-wide, do not treat it as a testing-quality initiative, and do not let it become the answer to "our date tests are flaky" — the reset convention and the time-source audit are the answers, and they are necessary with or without this package.
How can I help you explore Laravel packages today?