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

islambassiem's avatar

Pest Testing for DB::transaction

I have a database transaction in a controller as follows:

public function store(StoreRequestRequest $request, StoreRequestAction $action): JsonResource
{
    $insertedWorkflowRequest = DB::transaction(function () use ($request, $action) {
        $workflowRequest = $action->handle(Auth::user(), $request->validated());
        (new StoreStepsAction($workflowRequest))->handle();

        return $workflowRequest;
    });

    return new RequestResource($insertedWorkflowRequest);
}

Here are the actions:

public function handle(User $user, array $attributes): WorkflowRequest
{

    Gate::authorize('create', WorkflowRequest::class);

    $attributes['status'] = Status::PENDING;
    $attributes['priority'] = Priority::LOW;

    $request = $user->requests()->create($attributes);

    return $request->load('workflow', 'user', 'steps');
}

and

public function handle(): void
{
    $steps = WorkflowStep::where('workflow_id', $this->request->workflow_id)->get();
    foreach ($steps as $step) {
        WorkflowRequestStep::create([
            'workflow_request_id' => $this->request->id,
            'workflow_step_id' => $step->id,
            'approver_id' => $step->approver_id,
            'status' => Status::PENDING,
        ]);
    }
}

in the test file, I tested the creation of the first resource and it works fine, but when I test the second step in the database transaction it does not assert any assertions. Based on my search and understanding , once the first transaction happened, it is rolled back immediately and therefore the second one is not tested properly. My question is how to test the database transaction.

Here is my test (only for the first transaction)

test('authenticated user can create a request', function () {
    $this->seed([UserSeeder::class, WorkflowSeeder::class]);
    $user = User::factory()->create();
    $response = $this->actingAs($user)->postJson(route('requests.store'), [
        'workflow_id' => Workflow::first()->id,
    ]);

    $response->assertStatus(201);
    expect($response['status'])->toBe(Status::PENDING->value);
    expect($response['priority'])->toBe(Priority::LOW->value);
    expect($response['data'])->toBeNull();
});

What I have done is to create a unit test to test the second action separtely; I could not create a feature test for the transaction.

Here is the unit test

test('making a request make fill the relevant steps', function () {

Role::create(['name' => 'admin']);

$this->seed([UserSeeder::class, WorkflowSeeder::class, WorkflowStepSeeder::class]);

$request = WorkflowRequest::factory()->create();

(new StoreStepsAction($request))->handle();

$steps = WorkflowRequestStep::all();

expect($steps)->not->toBeNull();

});

0 likes
4 replies
Tray2's avatar

Why are you using transactions?

They are normally only used when there are multiple steps that all needs to be successfull before they are stored in the database, and I see no exception handling or rollbacks in your code.

islambassiem's avatar

@Tray2 Yes, this is exactly the case here

$workflowRequest = $action->handle(Auth::user(), $request->validated()); (new StoreStepsAction($workflowRequest))->handle();

To store StoreStepsAction, it must have a relevant $workflowRequest. Each $workflowRequest has many steps that need to be stored for that particular $workflowRequest.

krisi_gjika's avatar

are you sure you have WorkflowStep's in you testing database?

what if WorkflowStep::where('workflow_id', $this->request->workflow_id)->get() is just empty?

I don't think this has anything to do with transactions, as you only have 1 transaction. And if you can assert that WorkflowRequest is created you should be able to assert that WorkflowRequestStep's are created too (as long as the Workflow you have selected actually has steps)

Please or to participate in this conversation.