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

Novaunit Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

NovaUnit is a require-dev PHPUnit extension, not a runtime dependency. It gives you Nova-aware mock helpers and fluent assertions so you can test resources, fields, actions, filters, lenses, cards, and tools without booting a browser or issuing an HTTP request through Nova. The day-to-day payoff is a fast, CI-friendly signal on the admin surface you otherwise verify by clicking.

1. Check the version floor against your lockfile. Release 3.1.0 declares PHP 8.2 / Laravel 10 / PHPUnit 10. If you are on Laravel 11/12, PHPUnit 11, or PHP 8.3+, treat support as unproven and timebox a spike before you invest in writing tests you intend to keep.

composer require --dev joshgaber/novaunit:^3.1

2. Create one project-local base test case first. This is the highest-leverage ten minutes in the whole adoption. Every package-specific call routes through this class, so a fork, upgrade, or full replacement later is a one-file change rather than a suite-wide rewrite.

namespace Tests\Nova;

use JoshGaber\NovaUnit\TestCase as NovaUnitTestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;

abstract class NovaTestCase extends NovaUnitTestCase
{
    use RefreshDatabase; // required as soon as you assert on validation rules
}

3. Where to look, in order. The README's list of mock helpers and assertion names is the real API surface. Then read src/ for the Mock* classes — the assertions are thin wrappers, readable in a minute, and they tell you exactly what is assertable and what is not. Skip blog posts and Stack Overflow; helper names churned between 2.x and 3.x and will not match your installed version. Use the 3.1.0 release note only to confirm the supported matrix.

4. First use case: a resource shape spec. The lowest-friction, highest-signal test is "this resource exposes these fields." It catches accidental field removal, renames, and onlyOnDetail / hideFromIndex drift on day one.

namespace Tests\Nova;

use App\Nova\Post as PostResource;

class PostResourceTest extends NovaTestCase
{
    public function test_post_resource_exposes_expected_fields(): void
    {
        $this->mockResource(PostResource::class)
             ->assertHasFields(['id', 'title', 'status', 'author', 'published_at']);
    }
}

Run php artisan test --filter=PostResourceTest and confirm green against your actual Nova version before writing a second test. That run is your go/no-go signal for the whole adoption.

Implementation Patterns

Mirror the Nova tree. One test class per component under tests/Nova/, mirroring app/Nova/. Diffing the two directories gives you an instant coverage map, which matters more than a coverage percentage for admin surfaces ops depends on.

Configuration assertions on mocks; behavior assertions on real objects. Use mock helpers for configuration — which fields exist, which rules they carry, which actions are attached, visibility predicates. Instantiate the real class when you need behavior: resolve() output, calculate() results, handle() side effects. Mixing these is how suites get slow and brittle.

Field matrices belong in data providers. Field configuration is one assertion repeated with different arguments. Express it as data, not twenty near-identical methods.

/** @dataProvider requiredFields */
public function test_field_is_required(string $attribute): void
{
    $this->mockResource(PostResource::class)
         ->assertFieldHasRule($attribute, 'required');
}

public static function requiredFields(): array
{
    return [['title'], ['status'], ['author']];
}

Validation rules are the real payoff. This is the one thing NovaUnit does that is genuinely miserable by hand, because Nova fields carry rules as strings, arrays, or closures. Assert structural cases through the helper; for closure-based rules (->rules(fn ($request) => ...)) invoke the closure yourself with a faked NovaRequest and assert on the returned array — the helper will not resolve it for you.

Split action tests into definition and effect. Assert registration and configuration through the mock. Test the side effect by constructing the action and invoking handle() with real models and a properly faked request. The mock helper never executes anything.

public function test_publish_action_sets_published_at(): void
{
    $post = Post::factory()->create(['published_at' => null]);

    (new PublishPost())->handle(NovaRequest::create('/'), [$post->id]);

    $this->assertNotNull($post->fresh()->published_at);
}

Authorization as a role matrix. canSee / canRun closures are ordinary predicates. Loop roles against a single assertion to get an auditable spec instead of scattered per-role tests.

public function test_action_visible_only_to_editors(): void
{
    $action = new PublishPost();

    $this->assertTrue($action->authorizedToSee($this->novaRequestFor(User::factory()->editor()->create())));
    $this->assertFalse($action->authorizedToSee($this->novaRequestFor(User::factory()->viewer()->create())));
}

Filters, lenses, cards, tools get smoke-level tests only. Assert they exist and have a name/uri/component configured, then test any apply() logic directly against a query builder. Two assertions each is usually the right amount — deeper coverage here is almost always a change-detector.

Make the framework-upgrade run the deliverable. The day-to-day value of NovaUnit shows up at version-bump time. Before a Nova or Laravel upgrade, write characterization tests against current behavior, upgrade, and use red/green to decide roll-forward versus roll-back. Seed one deliberate regression during the pilot to prove the suite can actually fail — a green suite that cannot go red is worse than no suite.

Keep Nova tests in their own suite. They are fast (no HTTP bootstrapping), so run them on every PR touching app/Nova/** as a required check, but isolate them so a broken Nova assertion does not gate unrelated work.

Gotchas and Tips

Nova resolves through an authenticated Composer repository — solve CI before anything else. This is the #1 recurring friction and has nothing to do with NovaUnit. Your runner needs NOVA_USERNAME / NOVA_PASSWORD (or a mirrored artifact registry) before composer install resolves at all. If you cannot get Nova into CI, this package buys you local-only testing and the case weakens considerably.

Use NovaRequest, never a vanilla Request. Nova fields routinely assume route parameters and a resource key. Passing a plain Request into rules() or resolve() yields confusing null and undefined-index failures that look like package bugs. Build with NovaRequest::create(...) and set the resource/route context explicitly.

Mocks do not execute field logic. resolve(), calculate(), defaultValue(), and closure-based rules all need a real instance and usually a real model. If an assertion passes for something you expect to fail, confirm you are not asserting against a mock whose underlying object was never invoked.

The Nova registry is boot-time global state. Anything touching Nova::resources(), Nova::availableResources(), or Nova::sortResources() depends on NovaServiceProvider::boot() having run. Tests asserting on registration rather than a single class need the provider booted in setUp().

PHPUnit 10 lifecycle changes are a source migration, not a version bump. setUp(): void, static data providers, attributes replacing annotations (#[DataProvider]). Porting a 2.x NovaUnit suite across this boundary takes real time — budget for it.

Expect API name drift. Helper and assertion names moved between 2.x and 3.x. When a method "doesn't exist," read src/ for the current name instead of guessing or trusting a blog post.

Nova 5+ is unverified. 3.1.0 is explicit about Laravel 10 and PHPUnit 10 and silent about Nova majors. Validate against your exact Nova/PHP/Laravel matrix in a spike before writing tests you intend to keep.

Assume you own the exit path. 63 stars, zero dependents, one maintainer. Not disqualifying for a dev-only dependency — the worst case is a blocked pipeline, never an outage — but the swap cost is yours. Keeping all package usage inside NovaTestCase and project-local traits turns a replacement into a contained afternoon instead of a suite-wide rewrite.

Watch for over-mocking. A test that is nothing but mockResource(...)->assertHasField(...) is a change-detector for your own source file. Weight the suite toward rules, authorization, resolution output, and action side effects — the places where a regression actually breaks an ops workflow.

Debugging opaque failures. Dump the underlying instance from the mock and inspect its real field array. Nine times out of ten the field name is wrong, not the assertion — Nova derives field names from the property name, and they are case-sensitive.

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.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin