"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);
}
}
Save it as tests/Feature/DatabaseConnectionTest.php and run it from the project root:
vendor/bin/phpunit --filter DatabaseConnectionTest
On Windows (cmd or PowerShell):
vendor\bin\phpunit --filter DatabaseConnectionTest
The result:
RuntimeException: A facade root has not been set.
with the stack trace pointing into:
vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php
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.');
"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();
}
Correct:
protected function setUp()
{
parent::setUp();
DB::connection()->getPdo();
}
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();
}
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'],
];
}
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
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);
}
}
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
- 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');
});
}
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'));
}
}
This assumes a migration creates discussions. How RefreshDatabase behaves in 5.5:
-
MySQL or a file-based database: runs
migrate:freshonce 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
migratebefore 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;
}
}
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;
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'));
}
}
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);
}
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);
}
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();
}
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;
}
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;
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">
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
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
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
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
(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>
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:"/>
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>
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:
- 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. - Stop PHPUnit executing uncovered files while still reporting them:
<whitelist addUncoveredFilesFromWhitelist="true" processUncoveredFilesFromWhitelist="false">
<directory suffix=".php">./app</directory>
</whitelist>
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)