Description
Laravel Version
12.18.0
PHP Version
8.4
Database Driver & Version
pgsql,sqlite
Description
TLDR:
A work around until this can be fixed would be to provide test envs like this when running tests
docker compose exec laravel.test bash -c \
"APP_ENV=testing DB_CONNECTION=sqlite DB_DATABASE=':memory:' php artisan test -p $*"
Why this works:
It injects the test environment variables (APP_ENV, DB_CONNECTION, etc.) directly into the php process at runtime. This avoids conflicts where Laravel’s environment loader (phpdotenv) refuses to override variables already preloaded from .env by Sail.
This issue relates to a recently closed issue:
I was encountering the same problem and wanted to share my findings after digging deeper.
When running Laravel tests via Docker and Sail:
sail test --env=testing
Laravel silently falls back to .env values instead of .env.testing. This leads to issues like DB_CONNECTION=pgsql being used in tests, even though .env.testing specifies SQLite.
Laravel’s LoadEnvironmentVariables class correctly resolves the .env.testing file when --env=testing is passed. However, by that point, environment variables from .env have already been loaded and stored in $_SERVER by Sail:
# vendor/laravel/sail/bin/sail
if [ -n "$APP_ENV" ] && [ -f ./.env."$APP_ENV" ]; then
source ./.env."$APP_ENV"
elif [ -f ./.env ]; then
source ./.env
fi
This means Laravel’s env loader can't override them, due to phpdotenv’s immutability safeguard:
// Dotenv\Repository\Adapter\ImmutableWriter
public function write(string $name, string $value)
{
if ($this->isExternallyDefined($name)) {
return false;
}
}
That check considers any value already present in $_SERVER as externally defined, via:
// vendor/vlucas/phpdotenv/src/Repository/Adapter/ServerConstAdapter.php
public function read(string $name)
{
return Option::fromArraysValue($_SERVER, $name)
->filter(is_scalar)
->map((string) $value => ... );
}
Additionally, Pest's test runner reboots the app in child processes without CLI flags like --env=testing, making the issue even more persistent across runs.
here is an example stack trace showing the calls leading to this happening...
ServerConstAdapter.php:43, Dotenv\Repository\Adapter\ServerConstAdapter->read()
MultiReader.php:40, Dotenv\Repository\Adapter\MultiReader->read()
ImmutableWriter.php:108, Dotenv\Repository\Adapter\ImmutableWriter->isExternallyDefined()
ImmutableWriter.php:57, Dotenv\Repository\Adapter\ImmutableWriter->write()
AdapterRepository.php:87, Dotenv\Repository\AdapterRepository->set()
Loader.php:36, Dotenv\Loader\Loader::{closure:/var/www/html/vendor/vlucas/phpdotenv/src/Loader/Loader.php:27-46}()
Loader.php:27, array_reduce()
Loader.php:27, Dotenv\Loader\Loader->load()
Dotenv.php:224, Dotenv\Dotenv->load()
Dotenv.php:237, Dotenv\Dotenv->safeLoad()
LoadEnvironmentVariables.php:29, Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables->bootstrap()
Application.php:341, Illuminate\Foundation\Application->bootstrapWith()
Kernel.php:473, Illuminate\Foundation\Console\Kernel->bootstrap()
Kernel.php:195, Illuminate\Foundation\Console\Kernel->handle()
Application.php:1234, Illuminate\Foundation\Application->handleCommand()
artisan:13, {main}()
Temporary workaround:
Override the default env loader to use mutable dotenv:
// vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/LoadEnvironmentVariables.php
protected function createDotenv($app)
{
return Dotenv::createMutable(
$app->environmentPath(),
$app->environmentFile()
);
}
This allows .env.testing to override preloaded $_SERVER values, restoring expected behaviour during test execution. While this may not be a final solution (as mutability isn't ideal for production), it shows where the conflict lies.
Hopefully this helps highlight the problem and is useful when creating a fix.
Steps To Reproduce
Use sail with a laravel 12.18 project, make sure all composer dependencies are using the latest versions and pest tests and have both .env and .env.testing files in the root of the project with variables that should be over ridden by values in .env.testing then run tests on a running application using.
sail test --env=testing
a sample test case
<?php
it('checks the environment', function () {
dump([
'env' => app()->environment(),
'db' => config('database.connections.pgsql.database'),
'__env_file' => app()->environmentFile(),
]);
});
A sample .env file
APP_ENV=local
DB_CONNECTION=pgsql
DB_DATABASE=local
a sample .env.testing
APP_ENV=testing
DB_CONNECTION=sqlite
DB_DATABASE=:memory:
Unfortunately I already spent half a day figuring out why it wasn't working on my set up and I don't have any more free time to create a sample project, so I am just sharing everything I created along the way, I hope this will be enough.