DEV Community

Cover image for Laravel 5.5 PHPUnit: Fix 'A facade root has not been set'
Faizan
Faizan

Posted on Originally published at headsntail.com

Laravel 5.5 PHPUnit: Fix 'A facade root has not been set'

"A facade root has not been set." in a Laravel 5.5 PHPUnit test almost always means one thing: your test touched a facade like DB, Schema or Config before Laravel had booted. The fix usually takes a few minutes once you know where in the PHPUnit lifecycle the call happens.

Quick answer: Look for facade calls in three places: setUpBeforeClass(), @dataProvider methods, and your test class constructor. All three run before Laravel creates the application. Move those calls into setUp() after parent::setUp(), or into the test method itself. Then confirm the test extends Tests\TestCase, not PHPUnit's base TestCase.

This is an old, well-understood testing problem rather than a framework bug. Laravel 5.5 (released August 30, 2017) is long out of support, but plenty of production apps still run it, and their original PHPUnit suites still hit this error.

Why "A facade root has not been set" happens in PHPUnit

Here's a test that looks reasonable and fails every time:

<?php

namespace Tests\Feature;

use Tests\TestCase;
use Illuminate\Support\Facades\DB;

class DatabaseConnectionTest extends TestCase
{
    public static function setUpBeforeClass()
    {
        DB::connection()->getPdo();
    }

    public function test_database_connection()
    {
        $this->assertTrue(true);
    }
}
Enter fullscreen mode Exit fullscreen mode

Save it as tests/Feature/DatabaseConnectionTest.php and run it from the project root:

vendor/bin/phpunit --filter DatabaseConnectionTest
Enter fullscreen mode Exit fullscreen mode

On Windows (cmd or PowerShell):

vendor\bin\phpunit --filter DatabaseConnectionTest
Enter fullscreen mode Exit fullscreen mode

The result:

RuntimeException: A facade root has not been set.
Enter fullscreen mode Exit fullscreen mode

with the stack trace pointing into:

vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php
Enter fullscreen mode Exit fullscreen mode

What a facade root actually means

DB::table('users')->count() looks like a static call, but DB is a facade. When you call it, Laravel asks the application container for the real object behind it (the database manager). That real object is the facade's "root". If there is no application yet, the lookup returns nothing and Facade::__callStatic() throws:

throw new RuntimeException('A facade root has not been set.');
Enter fullscreen mode Exit fullscreen mode

"Root" has nothing to do with your MySQL root user, and the error doesn't mean your database password is wrong. Changing .env credentials won't fix an application that hasn't booted. I've seen people lose an hour there.

The application gets attached to facades during bootstrap: CreatesApplication calls $app->make(Kernel::class)->bootstrap(), and one of the bootstrappers it runs (RegisterFacades) calls Facade::setFacadeApplication($app). Anything that runs before that line gets this exception.

Why setUpBeforeClass() runs before Laravel boots

The cause is PHPUnit's execution order, not your query.

Stage When it runs Laravel app available?
@dataProvider methods When PHPUnit builds the suite, before any test class setup No
setUpBeforeClass() Once, before the class's first test No
setUp() before parent::setUp() Before each test No
setUp() after parent::setUp() Before each test Yes
Test method Each test Yes
tearDown() before parent::tearDown() After each test Yes
tearDown() after parent::tearDown() After each test No (app flushed)
tearDownAfterClass() Once, after the class No

In Laravel 5.5, Illuminate\Foundation\Testing\TestCase::setUp() calls refreshApplication() if $this->app isn't set yet, which calls your createApplication() from the CreatesApplication trait. That happens per test, inside the instance-level setUp(). setUpBeforeClass() is static and runs earlier, so there's no application for it to use.

Too early:

public static function setUpBeforeClass()
{
    DB::connection()->getPdo();
}
Enter fullscreen mode Exit fullscreen mode

Correct:

protected function setUp()
{
    parent::setUp();

    DB::connection()->getPdo();
}
Enter fullscreen mode Exit fullscreen mode

parent::setUp() has to come first. Code above it runs before Laravel exists.

You can confirm the flow in the Laravel 5.5 testing documentation and the Laravel 5.5 TestCase source.

Data providers: the cause people miss

This catches more people than setUpBeforeClass() does, because the provider looks like an ordinary instance method:

/**
 * @dataProvider userIdProvider
 */
public function test_user_has_profile($id)
{
    $this->assertNotNull(\App\User::find($id)->profile);
}

public function userIdProvider()
{
    // Fails: PHPUnit calls this while building the suite, before any setUp()
    return DB::table('users')->pluck('id')->map(function ($id) {
        return [$id];
    })->all();
}
Enter fullscreen mode Exit fullscreen mode

PHPUnit calls data providers while it builds the test suite, before setUpBeforeClass() and long before Laravel boots. Providers must return plain values. Move the lookup into the test:

/**
 * @dataProvider emailProvider
 */
public function test_user_lookup_by_email($email)
{
    $user = DB::table('users')->where('email', $email)->first();

    $this->assertNotNull($user);
}

public function emailProvider()
{
    return [
        ['admin@example.com'],
        ['staff@example.com'],
    ];
}
Enter fullscreen mode Exit fullscreen mode

The same rule applies to the test class constructor and to static properties initialised with facade calls: none of them can touch Laravel.

Why the test directory isn't the problem

A common guess is that the error happens because tests/ sits outside app/. It doesn't matter. Laravel 5.5's layout keeps them separate on purpose:

app/
bootstrap/
config/
database/
routes/
tests/
    Feature/
    Unit/
    CreatesApplication.php
    TestCase.php
phpunit.xml
Enter fullscreen mode Exit fullscreen mode

Tests\TestCase and the CreatesApplication trait are what connect the two. Moving test files into app/ won't fix a lifecycle problem.

Fix DB and Schema calls inside setUpBeforeClass

Step 1: Move the database call into the test

Open tests/Feature/DatabaseConnectionTest.php and replace it with:

<?php

namespace Tests\Feature;

use Tests\TestCase;
use Illuminate\Support\Facades\DB;

class DatabaseConnectionTest extends TestCase
{
    public function test_database_connection()
    {
        $pdo = DB::connection()->getPdo();

        $this->assertInstanceOf(\PDO::class, $pdo);
    }
}
Enter fullscreen mode Exit fullscreen mode

This only checks that the testing connection returns a PDO object; it doesn't create tables or touch records. I've left out the setUp() override entirely. An override that only calls parent::setUp() adds nothing, so add one only when you have real setup work to put after that line.

Step 2: Run it again

vendor/bin/phpunit --filter DatabaseConnectionTest
Enter fullscreen mode Exit fullscreen mode
  • If it passes: the lifecycle was the problem. Move on to the other tests that failed.
  • If you now get SQLSTATE[...]: that's progress. Laravel booted and reached the database. It's a configuration problem now (see the last section).
  • If you still get the facade error: the call is somewhere else. Read the first stack frame that points into your tests/ directory; it'll be a data provider, a constructor, or a helper loaded before boot.

Step 3: Fix Schema facade calls

Schema fails the same way:

public static function setUpBeforeClass()
{
    Schema::create('discussions', function ($table) {
        $table->increments('id');
    });
}
Enter fullscreen mode Exit fullscreen mode

Moving it below parent::setUp() stops the facade error but creates a new one: the second test fails with "table already exists" because the table is created before every test.

I'd use Laravel migrations rather than manually creating tables inside tests. If you're starting from an existing SQL schema, the SQL to Laravel migration converter saves the retyping. Then let RefreshDatabase (new in 5.5) handle it:

<?php

namespace Tests\Feature;

use Tests\TestCase;
use Illuminate\Support\Facades\Schema;
use Illuminate\Foundation\Testing\RefreshDatabase;

class DiscussionModelTest extends TestCase
{
    use RefreshDatabase;

    public function test_discussions_table_exists()
    {
        $this->assertTrue(Schema::hasTable('discussions'));
    }
}
Enter fullscreen mode Exit fullscreen mode

This assumes a migration creates discussions. How RefreshDatabase behaves in 5.5:

  • MySQL or a file-based database: runs migrate:fresh once per PHPUnit run (it drops every table in that database), then wraps each test in a transaction that's rolled back afterwards.
  • In-memory SQLite: runs migrate before every test, since the database disappears between tests.

That migrate:fresh is why the testing database must be separate. Point it at your real database and the first test run empties it.

What if you really need setup to run only once?

That's usually why people reach for setUpBeforeClass(). Use a static flag inside setUp() instead, after the app exists:

protected static $seeded = false;

protected function setUp()
{
    parent::setUp();

    if (! static::$seeded) {
        $this->artisan('db:seed', ['--class' => 'ReferenceDataSeeder']);
        static::$seeded = true;
    }
}
Enter fullscreen mode Exit fullscreen mode

Be careful combining this with RefreshDatabase: anything written in the first test's transaction is rolled back, so the seeded rows vanish for later tests. Either seed in every test (seeders are usually fast enough) or seed data that lives outside the transaction, such as in a migration.

Don't bootstrap a second Laravel application inside setUpBeforeClass() to work around this. It works until the two containers disagree about config or connections, and then it's painful to debug.

Fix the wrong TestCase import and missing parent setup

Mistake 1: Extending PHPUnit's TestCase directly

Check the import at the top of the failing file:

use PHPUnit\Framework\TestCase;
Enter fullscreen mode Exit fullscreen mode

That's fine for a pure unit test of a plain PHP class. It's wrong for any test that uses facades, models, the container or HTTP helpers, because PHPUnit's base class never boots Laravel. Use:

<?php

namespace Tests\Feature;

use Tests\TestCase;
use Illuminate\Support\Facades\Config;

class ConfigurationTest extends TestCase
{
    public function test_application_environment()
    {
        $this->assertSame('testing', Config::get('app.env'));
    }
}
Enter fullscreen mode Exit fullscreen mode

This one bites people who copy tests from newer Laravel versions, where the tests/Unit example extends PHPUnit's TestCase directly. The folder name doesn't decide the base class; what the test uses does.

Mistake 2: Forgetting parent::setUp() (or calling it last)

protected function setUp()
{
    Config::set('app.debug', false);
}
Enter fullscreen mode Exit fullscreen mode

This overrides Laravel's setup without running it. Same failure if parent::setUp() is there but below the facade call. The fix:

protected function setUp()
{
    parent::setUp();

    Config::set('app.debug', false);
}
Enter fullscreen mode Exit fullscreen mode

tearDown() is the mirror image: do your cleanup before parent::tearDown(), because Laravel flushes and discards the application inside it.

protected function tearDown()
{
    DB::table('temp_imports')->truncate();

    parent::tearDown();
}
Enter fullscreen mode Exit fullscreen mode

Laravel 5.5 ships with PHPUnit 6, where these methods have no return type. Don't copy protected function setUp(): void from modern examples. That signature is required from PHPUnit 8 and is a fatal error the other way round.

Mistake 3: Broken CreatesApplication setup

Check tests/TestCase.php. In a standard 5.5 project:

<?php

namespace Tests;

use Illuminate\Foundation\Testing\TestCase as BaseTestCase;

abstract class TestCase extends BaseTestCase
{
    use CreatesApplication;
}
Enter fullscreen mode Exit fullscreen mode

And tests/CreatesApplication.php does this (the original also lowers the bcrypt work factor to speed up tests):

$app = require __DIR__.'/../bootstrap/app.php';

$app->make(\Illuminate\Contracts\Console\Kernel::class)->bootstrap();

return $app;
Enter fullscreen mode Exit fullscreen mode

The bootstrap() line is the one that registers facades. If someone removed it, or TestCase no longer uses the trait, every facade fails. Compare with the original Laravel 5.5 tests scaffold, but review what your project customised before overwriting anything.

Mistake 4: Changing phpunit.xml's bootstrap

Laravel 5.5's phpunit.xml starts with (shortened):

<phpunit
    bootstrap="vendor/autoload.php"
    colors="true">
Enter fullscreen mode Exit fullscreen mode

Leave bootstrap="vendor/autoload.php" alone. Switching it to bootstrap/app.php to silence the error creates an application but never runs the console kernel's bootstrappers, and each test still creates its own app anyway. You end up with two apps and a harder bug.

Checklist before running the full suite

Check Why it matters
Test extends Tests\TestCase Boots Laravel for each test
No facades in setUpBeforeClass(), data providers or constructors They run before boot
parent::setUp() is the first line of setUp() Everything above it runs without an app
Cleanup comes before parent::tearDown() The app is gone afterwards
CreatesApplication calls bootstrap() That's where facades get their root
Config isn't cached locally Cached config ignores phpunit.xml
Testing database is isolated RefreshDatabase drops tables
PHP 7.x is on your PATH Laravel 5.5 / PHPUnit 6 don't support PHP 8

That last row matters on Windows. Recent XAMPP bundles PHP 8.x, and vendor\bin\phpunit uses whatever php is first on your PATH. Run php -v first; if it shows 8.x, you'll get unrelated deprecation and fatal errors that look like test failures. Point PATH at a PHP 7 install for this project rather than upgrading PHPUnit inside a 5.5 app.

Run the single failing test first, then the whole suite:

vendor/bin/phpunit
Enter fullscreen mode Exit fullscreen mode

php artisan test doesn't exist in 5.5 (it arrived in Laravel 7), so use PHPUnit directly.

Laravel 5.5's security fixes ended on August 30, 2020. Fixing the test suite is worth doing, and it's the safety net you need before an upgrade, but it doesn't make the framework supported again. If you're also moving the server's PHP version, the PHP security update guide for cPanel and XAMPP covers that side.

What to watch when the facade error disappears

Once Laravel boots, tests can reach code they never reached before, so new errors are normal.

Cached config ignores your phpunit.xml

Check this before you run anything that uses RefreshDatabase. If bootstrap/cache/config.php exists, Laravel loads it and skips .env and the <env> values in phpunit.xml. Your tests then run with APP_ENV=local and your real database connection, and migrate:fresh wipes your development data.

Check and clear it:

php artisan config:clear
Enter fullscreen mode Exit fullscreen mode

That deletes bootstrap/cache/config.php. Do it on your development machine, not on a live server where you deliberately cached config during deployment (as in the Laravel cPanel deployment checklist).

Database connection errors

If the error changes to:

SQLSTATE[HY000] [1045] Access denied for user
Enter fullscreen mode Exit fullscreen mode

the credentials Laravel is using are wrong for the testing database. If it's:

SQLSTATE[HY000] [2002] No connection could be made because the target machine actively refused it
Enter fullscreen mode Exit fullscreen mode

(or Connection refused on Linux), MySQL isn't running. On XAMPP, if MySQL won't stay up, see MySQL shutdown unexpectedly in XAMPP.

Laravel 5.5's default phpunit.xml sets:

<php>
    <env name="APP_ENV" value="testing"/>
    <env name="CACHE_DRIVER" value="array"/>
    <env name="SESSION_DRIVER" value="array"/>
    <env name="QUEUE_DRIVER" value="sync"/>
</php>
Enter fullscreen mode Exit fullscreen mode

None of those isolate the database. The simplest isolated option is in-memory SQLite. Add these two lines inside <php>:

<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>
Enter fullscreen mode Exit fullscreen mode

This needs the pdo_sqlite extension (enabled by default in XAMPP's php.ini). It's fast, but SQLite isn't MySQL: migrations that use MySQL-only column types or dropColumn() (which needs doctrine/dbal on SQLite) can fail. If that's your situation, create a separate MySQL database such as myapp_testing and set DB_DATABASE to that instead.

Code coverage triggers the same error

Sometimes normal runs pass, but vendor/bin/phpunit --coverage-html coverage fails with the facade error. Laravel 5.5's phpunit.xml has:

<filter>
    <whitelist processUncoveredFilesFromWhitelist="true">
        <directory suffix=".php">./app</directory>
    </whitelist>
</filter>
Enter fullscreen mode Exit fullscreen mode

processUncoveredFilesFromWhitelist="true" makes PHPUnit include every file under app/ that no test touched, before Laravel boots. A helper file with a top-level Config::get() or DB:: call blows up at that point.

Two fixes, best first:

  1. Remove the top-level facade call from that file. Wrap it in a function or move it into a service provider's boot(). Files that run framework code on include are fragile anyway.
  2. Stop PHPUnit executing uncovered files while still reporting them:
<whitelist addUncoveredFilesFromWhitelist="true" processUncoveredFilesFromWhitelist="false">
    <directory suffix=".php">./app</directory>
</whitelist>
Enter fullscreen mode Exit fullscreen mode

Read the stack trace first to find which file is being included. Excluding a whole directory from coverage hides the problem and shrinks what the report measures.

Standalone Eloquent is a different setup

Some projects use Eloquent without a full Laravel app, through Illuminate\Database\Capsule\Manager. That's valid, but it isn't the same as booting Laravel. Capsule doesn't register every facade. If you're testing a full Laravel 5.5 application, I'd fix the normal bootstrap rather than add a second database manager just to make the exception go away.

For legacy apps that need hands-on debugging, HeadsNTail services offers practical development help.

FAQ

Why does Laravel say "A facade root has not been set."?

A facade couldn't find the application object behind it. In PHPUnit, that almost always means DB, Schema, Config or a model was used before the Laravel application booted: in setUpBeforeClass(), a data provider, a constructor, or setUp() above parent::setUp().

Can I use DB::connection() inside setUpBeforeClass()?

Not with the standard Laravel 5.5 test setup. setUpBeforeClass() is static and runs before Laravel creates the per-test application. Put the call in setUp() after parent::setUp(), or in the test method. For one-time setup, use a static flag inside setUp().

Can I use facades or models in a PHPUnit data provider?

No. PHPUnit calls data providers while building the suite, before any setup method runs. Return plain values (IDs, emails, arrays) from the provider and do the database lookup inside the test.

Should Laravel unit tests extend PHPUnit's TestCase or Tests\TestCase?

Use PHPUnit\Framework\TestCase only for tests of plain PHP classes that never touch Laravel. Use Tests\TestCase whenever the test uses facades, models, the container, the database or HTTP helpers. Being in tests/Unit doesn't change that.

Still stuck? Find the first stack frame inside your own tests/ folder, check which lifecycle stage it runs in, and rerun only that test before changing anything else.


Originally published at headsntail.com. Free tools for PHP, Laravel and cPanel work: headsntail.com/tools.

Top comments (0)