Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

KangSamli's avatar

Integrity constraint violation when testing using Pest php

test('admin cannot delete a category that is in use by an idea', function () { $admin = User::factory()->create(['role' => 'admin']); $category = Category::factory()->create(); Idea::factory()->for($category)->pending()->create(); // Idea uses this category

$response = $this->actingAs($admin)->delete(route('categories.destroy', $category));

$response->assertRedirect(); // Redirects back
$response->assertSessionHas('status', 'error');
$this->assertDatabaseHas('categories', ['id' => $category->id]); // Category still exists

});

I wrote this function to test Category Management feature. But when i run php artisan test, it return error value with Integrity constraint violation message. Can anyone help me to solve this?

0 likes
8 replies
JussiMannisto's avatar

You're creating an idea that reference a category. Deleting that category would cause a constraint violation, and you'll get an exception.

If you want to handle those errors gracefully, you'll need to catch query exceptions in your controller method. But I'm not a fan of that because query exceptions can be caused by other things too. I'd rather check if there are any associated ideas before deletion, and block the action with an error message if needed.

Tray2's avatar

You can add cascade to the foreign key, if you then delete the category, it will delete the idea as well.

$table->foreignId('user_id')
    ->constrained()
    ->onUpdate('cascade')
    ->onDelete('cascade');
newbie360's avatar

May be use policy?

public function delete(User $user, Category $category): bool
{
    if (! $user->isAdmin()) {
        return false;
    }
    
    return match (true) {
        $category->hasAttribute('ideas_count') => $category->ideas_count === 0,
        $category->hasAttribute('ideas_exists') => ! $category->ideas_exists,
        default => $category->ideas()->doesntExist(),
    };
}
newbie360's avatar

@JussiMannisto Hmm, the user shouldn't see the delete url or button at the first place?

@foreach ($categories as $category)
    @can('delete', $category)
        // url or button for delete
    @endcan
@endforeach

remember eager loading the counts or exists. otherwise it cause N+1 issues

JussiMannisto's avatar

@newbie360 This isn't about the UI. The original poster is writing a feature test.

Hiding the button isn't sufficient. What if the user has added an idea on another browser tab? Or what if they're part of a team, and another user just added an idea?

An authorization error (403) is wrong and would mislead the user. The correct error would be 409 Conflict with a proper error message, so that the user understands what's going on.

newbie360's avatar

@JussiMannisto There also should be have a Gate check to protecting the Route or the endpoint.

for me, i wouldn't expose too much infos to the client, most response is 401, 403, 404, yes that's all, and logging all the strange behavior instead.

for the testing, just use factory create dummy data and trigger the endpoint, assert see error, assert database record still exists, Done.

JussiMannisto's avatar

@newbie360 There of course needs to be a policy, and I assume it's there already. But that's for authorization, and this isn't an authorization issue.

It's really bad practice to return incorrect status codes. Which one of those do you use when validation fails? Or when rate limit is exceeded, XSRF token is rejected, or a JS asset has been updated?

As for your testing suggestion, that's exactly what OP tried. It doesn't work because it triggers a 500 error, and there's no good programmatic way to differentiate it from unexpected query exceptions, which should be reported to developers.

And if an action fails, the user must see why it fails. Otherwise you'll frustrate them and clog up customer support. Just do the right thing.

Please or to participate in this conversation.