Database cache driver does not work correctly with parallel tests
#54,716 opened on Feb 19, 2025
Description
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
laravel new, no starter kit, select Pest for instance- 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"/> touch database/tests.sqlite- Force cache store creation in a service provider:
// AppServiceProvider::boot() cache()->getStore(); - Add a test:
test('foobar', function () { expect(DB::select('select * from users'))->toHaveCount(0); User::create(['name' => 'John Doe', 'email' => 'foo@bar.com', '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); }); artisan test -pshould fail on the last assertion- If you remove the service provider call, the test passes