joshgaber/novaunit
NovaUnit provides PHPUnit-friendly helpers for testing Laravel Nova resources, fields, actions, filters, cards, and requests, letting you assert Nova behavior in your test suite with less boilerplate.
Architecture fit. NovaUnit is a require-dev PHPUnit extension, not a runtime dependency — it never enters the production artifact. That is the single most important architectural fact for a TPM: the worst-case failure is a blocked pipeline, never a customer-facing outage. It slots alongside Laravel's existing test suite rather than replacing anything, so there is no new runner, no new framework, and no new CI concept to socialize. The package's job is narrow and legible: expose Nova resources, fields, actions, filters, lenses, cards, and tools as testable objects with fluent assertions. Where an organization has real business logic inside Nova (validation rules, authorization predicates, action side effects, computed metrics), this converts an untestable surface into a schedulable engineering item. Where Nova is stock CRUD, the fit is weak — the assertions become change-detectors over your own source files.
Integration feasibility. Adoption is a one-line composer require --dev plus a project-local base test case, and a single mockResource(...)->assertHasFields([...]) test run acts as the go/no-go gate before any real test-writing investment. The recommended pattern — funnel every package-specific call through a project-owned NovaTestCase — makes the exit path a one-file change rather than a suite-wide rewrite. The blocking prerequisite is unrelated to the package: Nova resolves through an authenticated Composer repository, so CI runners need NOVA_USERNAME/NOVA_PASSWORD or a mirrored artifact registry before composer install resolves at all. If that credential path cannot be solved, this package buys local-only testing and the ROI case weakens materially. That is the first thing to validate, not the last.
Technical risk. Four risks matter at the portfolio level. First, staleness: last release 2023-02-21, with 3.1.0 explicitly targeting PHP 8.2 / Laravel 10 / PHPUnit 10. Teams already on Laravel 11/12, PHPUnit 11+, PHP 8.3+, or Nova 5 are in unproven territory and must timebox a compatibility spike. Second, bus factor: 63 stars, zero dependents, one maintainer. Mitigating evidence is that the maintainer executed the non-trivial PHPUnit 10 lifecycle port (static data providers, setUp(): void, attributes over annotations) — a functioning maintenance pulse as of the last release, so the risk reads "low bus factor, currently alive" rather than "possibly abandoned." Third, over-mocking: a suite of pure mockResource()->assertHasField() tests has near-zero regression value and inflates maintenance cost. Fourth, API name drift across 2.x → 3.x — blog posts and Stack Overflow answers will not match the installed version; src/ is the source of truth.
Key questions.
NovaTestCase) if the package goes dormant?Stack fit. PHPUnit-based Laravel test suites are the target and the only requirement. NovaUnit extends Laravel's test case, so it inherits factories, database refresh traits, and existing test infrastructure. The supported combination as of 3.1.0 is PHP 8.2 / Laravel 10 / PHPUnit 10 — the case most likely to go green on first attempt. Teams on PHPUnit 9 should note that the package no longer forces a pin, which means the admin-coverage decision can run alongside a test-stack modernization rather than queued behind it.
Migration path. Incremental by design — there is nothing to migrate. Recommended shape:
tests/Nova/NovaTestCase.php extending the package test case, with RefreshDatabase for rule assertions.app/Nova/ under tests/Nova/ — directory diff becomes a coverage report.Compatibility. Two boundaries to manage. The upgrade boundary: any Nova, Laravel, or PHP major bump is where this package's version floor bites — validate the matrix in a spike before committing to test-writing effort you intend to keep. The lifecycle boundary: porting a 2.x suite across the PHPUnit 10 change is a real migration, not a version bump, and should be estimated as such. Field logic (resolve(), calculate(), closure-based rules()) is not executed by mocks and needs real instances plus NovaRequest — a plain Request produces confusing null failures that look like package bugs and consume support time.
Sequencing. Do the CI credential work first. Then the base test case. Then a single go/no-go test. Only then scale test-writing. If a Nova or Laravel upgrade is on the roadmap, write characterization tests against current behavior before the upgrade — that turns the upgrade itself into the deliverable and gives a roll-forward/roll-back signal instead of a date committed on optimism. Seed one deliberate regression during the pilot to prove the suite can actually go red; a green suite that cannot fail is worse than no suite.
Maintenance. Ongoing cost is test-writing discipline, not package upkeep. The suite is fast (no HTTP bootstrapping), so the runtime budget is low and it can run as a required check on every PR touching app/Nova/**. The real maintenance lever is scoping: weight effort toward validation rules, authorization predicates, resolution output, and action side effects — the things where a regression breaks an ops workflow — and cap filters, lenses, cards, and tools at two smoke assertions each. Deeper coverage there is change-detection over your own file. Keep Nova tests in their own suite so a broken Nova assertion does not gate unrelated work.
Support. Support burden is low and internal: this is a dev-only dependency with no production surface, no on-call implication, and no customer-visible failure mode. The one recurring friction is environmental — the authenticated Nova Composer repo — and it should be solved once, documented, and owned rather than rediscovered per engineer or per CI migration. When a helper "doesn't exist," the answer is to read src/, not to search the web; naming has churned across majors and stale answers will burn hours.
Scaling. Return scales with custom Nova surface area, not with headcount or traffic. Teams with heavily customized resources, actions, authorization logic, and computed fields get outsized value; stock-CRUD teams get very little and should deprioritize. Test volume scales cheaply because the suite is fast, but assertion value does not scale automatically — a growing pile of field-existence assertions adds maintenance drag without adding regression protection. The scaling constraint is reviewer discipline about what gets asserted, which is a policy decision a TPM can set.
Failure modes.
NovaTestCase base class: replacement is a one-file change, not a rewrite. Blast radius is a blocked pipeline, never an outage.NovaRequest.Ramp-up. Very low. One base class, one example test, one README read — an engineer is productive in an afternoon, and the fluent assertion style is legible to reviewers who have never touched the package. The genuinely non-obvious knowledge is concentrated in four gotchas worth writing into a short internal note: use NovaRequest (never vanilla Request); mocks do not execute field logic; the Nova registry is boot-time global state requiring NovaServiceProvider::boot() in setUp(); and field names are case-sensitive and derived from the property name, so most "package bugs" are wrong field names. None of this requires training budget — it requires a page of documentation and one person who has read src/.
How can I help you explore Laravel packages today?