Skip to content

.env.testing Values Ignored During sail test --env=testing Due to Early Preloading of .env into $_SERVER #56063

Open
@phpws

Description

@phpws

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:

#55532

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.

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