Skip to content

Database cache driver does not work correctly with parallel tests #54716

@stancl

Description

@stancl

Laravel Version

11.x

PHP Version

any

Database Driver & Version

irrelevant

Description

This is similar to #53692 but a separate issue.

It seems creating a DB connection too early (in a service provider, in a specific way) and using it later causes some weird "desynchronization" when using parallel tests.

I haven't had the time to do a deep dive yet, but the issue is likely related to some reconnecting logic similar to the linked issue.

The reproduction steps may seem very specific, i.e. why am I instantiating a cache store in a service provider. In practice this happens if you just use RateLimiter::for('foo', static fn () => null), but that under the hood creates the cache store which seems to be the underlying cause. So the reproduction steps are as low level as I managed to trace this issue.

In a real app, e.g. one based on Fortify which rate limits logins, any call to cache() while using the database cache driver, will cause weird behavior. There is a test in Jetstream that directly reproduces this (it makes a request to the login route) but the side effects don't have a perceptible effect. If there were some more DB-related assertions after the request, especially comparing DB state before the request and after, you'd see it.

Steps To Reproduce

  1. laravel new, no starter kit, select Pest for instance
  2. Make the following changes in phpunit.xml:
    <env name="CACHE_STORE" value="database"/>
    <env name="DB_CONNECTION" value="sqlite"/>
    <env name="DB_DATABASE" value="database/tests.sqlite"/>
  3. touch database/tests.sqlite
  4. Force cache store creation in a service provider:
    // AppServiceProvider::boot()
    cache()->getStore();
  5. Add a test:
    test('foobar', function () {
        expect(DB::select('select * from users'))->toHaveCount(0);
        User::create(['name' => 'John Doe', 'email' => '[email protected]', 'password' => 'foobar']);
        expect(DB::select('select * from users'))->toHaveCount(1);
    
        cache()->put('foo', 'bar');
        expect(cache('foo'))->toBe('bar');
    
        // users table is empty!
        expect(DB::select('select * from users'))->toHaveCount(1);
    });
  6. artisan test -p should fail on the last assertion
  7. If you remove the service provider call, the test passes

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions