Activity Log Logo
Testing

Testing

Test patterns for the timeline component, relation manager, action, and builder.

The package's own test suite uses Pest 4 with pest-plugin-laravel and pest-plugin-livewire — the same plugins your consumer app already has if it tests Filament. Nothing extra to install. This page covers patterns for testing the timeline inside your application; for testing the package itself, see tests/Feature/ in the repo.

Setup

The base case in your consumer app is the standard Laravel test scaffold:

// tests/Pest.php
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

uses(TestCase::class, RefreshDatabase::class)->in('Feature');

A typical beforeEach for any test that touches the timeline UI logs in an admin and creates the record under test:

use App\Models\Person;
use App\Models\User;

beforeEach(function (): void {
    $this->actingAs(User::factory()->admin()->create());
    $this->record = Person::factory()->create();
});

Testing the infolist via livewire()

Mount the Filament ViewRecord page that hosts your ActivityLog::make() component, mutate the record so spatie writes an activity row, and assert the rendered output. The built-in ActivityLogRenderer emits the diff sentence, so assert against the new value:

use App\Filament\Resources\People\Pages\ViewPerson;

use function Pest\Livewire\livewire;

it('renders an entry on the person view page after an update', function (): void {
    $this->record->update(['name' => 'Updated name']);

    livewire(ViewPerson::class, ['record' => $this->record->getKey()])
        ->assertSeeHtml('Updated name');
});

If you've registered a custom renderer (see /essentials/customization), assert against your specific markup instead — the assertSeeHtml() target should match what your renderer outputs, not the diff sentence.

Testing the relation manager

The package's own RelationManagerTest.php asserts the static configuration surface; your consumer-app test should mount the relation manager against an owner record and a host page. Filament requires both ownerRecord and pageClass:

use App\Filament\Resources\People\Pages\EditPerson;
use Relaticle\ActivityLog\Filament\RelationManagers\ActivityLogRelationManager;

use function Pest\Livewire\livewire;

it('mounts the activity log relation manager for a record', function (): void {
    livewire(ActivityLogRelationManager::class, [
        'ownerRecord' => $this->record,
        'pageClass' => EditPerson::class,
    ])->assertSuccessful();
});

Adjust pageClass to whichever resource page hosts the relation manager (typically Edit* or View*).

Testing the header action

The default action name is 'activityLog' — that's the string you pass to callAction():

use App\Filament\Resources\People\Pages\ViewPerson;

use function Pest\Livewire\livewire;

it('opens the activity log slide-over', function (): void {
    livewire(ViewPerson::class, ['record' => $this->record->getKey()])
        ->callAction('activityLog')
        ->assertSuccessful();
});

If you renamed the action via ActivityLogAction::make('history'), pass 'history' instead.

Asserting timeline entries directly

Skip the UI when you're testing source composition — call the builder and assert against its return value. This mirrors the package's own ActivityLogSourceTest.php:

use Spatie\Activitylog\Models\Activity;

it('emits an entry per activity row', function (): void {
    Activity::create([
        'log_name' => 'default',
        'description' => 'updated',
        'event' => 'updated',
        'subject_type' => $this->record->getMorphClass(),
        'subject_id' => $this->record->getKey(),
        'properties' => ['attributes' => ['name' => 'New'], 'old' => ['name' => 'Old']],
    ]);

    $entries = $this->record->timeline()->fromActivityLog()->get();

    expect($entries)
        ->toHaveCount(1)
        ->and($entries->first()->event)->toBe('updated');
});

The same shape works for fromRelation(), fromActivityLogOf(), and fromCustom() — drive the builder, collect the entries, assert against $entries->pluck('event'), ->count(), or any other property on TimelineEntry. The package's BuilderFilteringTest.php is a good reference for between(), ofEvent(), and sortByDateAsc() patterns.

Factories for Spatie\Activitylog\Models\Activity

Spatie's Activity model doesn't ship a factory. If you need one in your consumer app, drop this in database/factories/:

namespace Database\Factories\Spatie;

use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Database\Eloquent\Model;
use Spatie\Activitylog\Models\Activity;

/** @extends Factory<Activity> */
final class ActivityFactory extends Factory
{
    protected $model = Activity::class;

    public function definition(): array
    {
        return [
            'log_name' => 'default',
            'description' => $this->faker->word(),
            'event' => $this->faker->randomElement(['created', 'updated', 'deleted']),
            'subject_type' => null,
            'subject_id' => null,
            'causer_type' => null,
            'causer_id' => null,
            'properties' => [],
        ];
    }

    public function for(Model $subject): static
    {
        return $this->state([
            'subject_type' => $subject->getMorphClass(),
            'subject_id' => $subject->getKey(),
        ]);
    }
}

Wire spatie's Activity to the factory by overriding Laravel's factory resolver in a service provider's boot() method — see Laravel's factory docs for the canonical setup with Factory::guessFactoryNamesUsing(). Then use it like any other factory:

use Database\Factories\Spatie\ActivityFactory;

ActivityFactory::new()->for($this->record)->create();

Where to look next

  • The package's own tests/Feature/ directory has working examples for every public surface.
  • For UI-level assertions on grouping, pagination, and infinite scroll, mount ActivityLogLivewire directly — see RelationManagerTest.php for the static-config approach.
  • For end-to-end flows (action opens slide-over, scroll loads more entries), use Pest 4 browser tests with visit() instead of livewire().
Copyright © 2026