Skip to content

Testing

Tachi Kit is tested with Pest. Feature tests cover every page, guard and command in the kit.

Terminal window
php artisan test --compact # everything
php artisan test --compact tests/Feature/UsersTest.php # one file
php artisan test --compact --filter="deletes a user" # one test

Tests run against an in-memory SQLite database (phpunit.xml). Every feature test uses RefreshDatabase automatically (tests/Pest.php), so each test starts empty.

In CI, composer ci:check runs the tests together with formatting, linting, type checks and PHPStan. See Development Workflow.

Database\Factories\UserFactory has states for the kit’s common cases:

State Result
asSuperadmin() Has the superadmin role
asAdmin() Has the admin role
asUser() Has the user role
withRole(RoleName|string $role) Has any role, created if missing
inactive() is_active = false
unverified() email_verified_at = null
withTwoFactor() Two-factor authentication configured
$admin = User::factory()->asAdmin()->create();
$jane = User::factory()->asUser()->inactive()->create(['name' => 'Jane Doe']);

Roles only have their permissions once RolePermissionSeeder has run. Seed it in beforeEach for tests that depend on permissions:

beforeEach(function () {
$this->seed(RolePermissionSeeder::class);
});

Test both the allowed and the forbidden case, and include a superadmin where the Gate::before bypass has exceptions:

test('admins cannot delete a superadmin', function () {
$superadmin = User::factory()->asSuperadmin()->create();
$this->actingAs(User::factory()->asAdmin()->create())
->delete(route('users.destroy', $superadmin))
->assertForbidden();
});

For one-off roles, create a role with exactly the permissions under test:

$role = Role::query()->create(['name' => 'Role Manager']);
$role->givePermissionTo(Permission::RolesEdit->value);
$user = User::factory()->create()->assignRole($role);
$this->actingAs($admin)
->get(route('users.index', ['search' => 'jane']))
->assertOk()
->assertInertia(fn (Assert $page) => $page
->component('users/index')
->has('users.data', 1)
->where('users.data.0.name', 'Jane Doe')
);

Deferred props (like the dashboard’s) are loaded with loadDeferredProps():

->assertInertia(fn (Assert $page) => $page
->missing('stats')
->loadDeferredProps(fn (Assert $reload) => $reload->where('stats.total_users', 4))
);
$this->post(route('roles.store'), $payload)
->assertInertiaFlash('toast', ['type' => 'success', 'message' => 'Role Auditor created.']);

Query the Activity model by event:

use Spatie\Activitylog\Models\Activity;
$activity = Activity::query()->where('event', ActivityEvent::UserDeactivated->value)->sole();
expect($activity->subject_id)->toBe($jane->id)
->and(ActivityEvent::UserDeactivated->describe($activity))->toBe('Deactivated user Jane Doe');

actingAs() doesn’t fire the Login event, so only tests that post to login.store produce login entries.

Pin the clock with $this->travelTo(...) so date-based results (like the dashboard’s monthly chart) are deterministic.

When you change a guard on purpose, for example letting admins view the activity log, update the test that asserted the old behavior instead of deleting it. Each test in the suite documents a decision.