Skip to content

Conversation

@lucasferro0
Copy link

Transaction Control in HTTP Tests

📋 Overview

This feature adds automatic database transaction control during HTTP tests when the DatabaseTransactions trait is used. The implementation ensures that the transaction level is always reset to 1 after each HTTP request in tests, avoiding issues with uncommitted nested transactions.

🔧 Technical Changes

Modified Files

1. src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php

New Method Added:

protected function resetDatabaseTransactionLevelToOne()
{
    $uses = $this->traitsUsedByTest ?? array_flip(class_uses_recursive(static::class));

    if (! isset($uses[DatabaseTransactions::class])) {
        return;
    }

    $databaseManager = $this->app['db'];
    $connections = $this->connectionsToTransact();

    foreach ($connections as $connectionName) {
        if ($databaseManager->connection($connectionName)->transactionLevel() > 1) {
            $databaseManager->connection($connectionName)->rollBack(1);
        }
    }
}

Updated HTTP Methods:

All HTTP methods in the trait have been updated to call resetDatabaseTransactionLevelToOne() after executing the request:

  • get()
  • getJson()
  • post()
  • postJson()
  • put()
  • putJson()
  • patch()
  • patchJson()
  • delete()
  • deleteJson()
  • options()
  • optionsJson()
  • head()

Implementation Example:

public function get($uri, array $headers = [])
{
    $server = $this->transformHeadersToServerVars($headers);
    $cookies = $this->prepareCookiesForRequest();

    $testResponse = $this->call('GET', $uri, [], $cookies, [], $server);

    $this->resetDatabaseTransactionLevelToOne();

    return $testResponse;
}

2. tests/Integration/Foundation/Testing/Concerns/MakeHttpRequestsWithDatabaseTransactionTest.php (New File)

Complete test file with 620 lines containing 22 test scenarios covering:

  • All HTTP methods (GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD)
  • JSON variants of each method
  • Scenarios with transaction without commit
  • Scenarios with transaction with commit

🎯 Problem Solved

Before Changes

When you used the DatabaseTransactions trait in your tests and made HTTP requests that started new transactions (with DB::beginTransaction()), the framework did not properly manage the nested transaction level. This caused:

  1. Uncommitted nested transactions: If application code started a transaction but didn't commit or rollback, that transaction remained "hanging"
  2. Inconsistent state: Transaction level remained greater than 1 after the request
  3. Unpredictable tests: Depending on whether the code committed or not, data could be persisted unexpectedly

Example of the Problem:

class UserTest extends TestCase
{
    use DatabaseTransactions;

    public function test_create_user()
    {
        Route::post('users', function () {
            DB::beginTransaction();  // Starts nested transaction (level 2)
            
            User::create(['name' => 'John']);
            
            // Forgot to commit or rollback!
            return response()->json(['success' => true]);
        });

        $response = $this->postJson('users');
        
        // Problem: transaction is still at level 2
        // User was not saved, but test may fail confusingly
        $this->assertDatabaseMissing('users', ['name' => 'John']); // ❌ Fails
    }
}

After Changes

The framework now automatically detects and resets nested transactions to level 1 after each HTTP request, ensuring that:

  1. Uncommitted transactions are rolled back: Automatic rollback to level 1
  2. Committed transactions persist: If code committed, data is saved
  3. Predictable behavior: Test always starts each request with clean transaction level

Same Example, Now Working:

class UserTest extends TestCase
{
    use DatabaseTransactions;

    public function test_create_user_without_commit()
    {
        Route::post('users', function () {
            DB::beginTransaction();
            
            User::create(['name' => 'John']);
            
            // Did not commit
            return response()->json(['success' => true]);
        });

        $response = $this->postJson('users');
        
        // ✅ Transaction reset to level 1 automatically
        // ✅ Nested transaction rollback was executed
        $this->assertDatabaseMissing('users', ['name' => 'John']); // ✅ Passes
    }
}

✅ Why It Doesn't Break Existing Features

1. Full Backward Compatibility

The feature is opt-in through the DatabaseTransactions trait:

  • If you don't use DatabaseTransactions: Nothing changes. Behavior is exactly the same.
  • If you use DatabaseTransactions: Feature only adds additional behavior that improves transaction management.
// Test WITHOUT DatabaseTransactions - no changes
class SimpleTest extends TestCase
{
    public function test_something()
    {
        $this->get('/'); // Behavior unchanged
    }
}

// Test WITH DatabaseTransactions - feature enabled
class TransactionalTest extends TestCase
{
    use DatabaseTransactions; // Feature enabled here
    
    public function test_something()
    {
        $this->get('/'); // Now with transaction management
    }
}

2. Only Fixes Problematic Behavior

The feature doesn't change expected behavior, it only fixes a bug where nested transactions weren't managed correctly. Previous behavior was inconsistent and caused hard-to-debug bugs.

3. Respects Explicit Commits

If application code explicitly commits, the feature respects that decision:

// Commits still work normally
DB::beginTransaction();
User::create(['name' => 'Test']);
DB::commit(); // ✅ Respected - data is saved

4. Doesn't Interfere with Level 1 Transactions

Feature only acts when there are nested transactions (level > 1). The main transaction from DatabaseTransactions (level 1) remains intact:

// Level 1: managed by DatabaseTransactions trait
// Level 2+: managed by new feature

@lucasferro0 lucasferro0 force-pushed the feature/transaction-control-in-tests-http-calls branch from 991c5a9 to 62b2504 Compare December 8, 2025 20:57
@taylorotwell
Copy link
Member

Thanks for your pull request to Laravel!

I appreciate you taking the time to submit this; however, it appears this contribution may have been primarily AI-generated without careful human review and consideration.

We've found that AI-generated code often doesn't align well with Laravel's conventions, architectural decisions, and the specific context of what we're trying to accomplish with the framework. Quality contributions require thoughtful human insight into the codebase.

If you're interested in contributing to Laravel, I'd encourage you to familiarize yourself with the existing codebase, engage with the community, and submit PRs that reflect your own understanding and careful consideration of the problem you're solving.

@lucasferro0
Copy link
Author

lucasferro0 commented Dec 8, 2025

@taylorotwell Oh, I'm sorry. I used AI only for pr description. I don't knew about this community rule. Can I adjust pull request and do again submit ? This pull request would solve one of the problems that recently occurred in one of the projects I contribute to.

@lucasferro0 lucasferro0 deleted the feature/transaction-control-in-tests-http-calls branch December 8, 2025 23:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants