TestDatabases.php
TLDR
This file, TestDatabases.php
, is located in the Illuminate\Testing\Concerns
namespace and contains a trait called TestDatabases
. The trait provides methods for managing test databases in the testing environment. It includes methods for booting a test database, ensuring a test database exists, ensuring the schema of the test database is up to date, and switching between databases.
Methods
bootTestDatabase
This method is responsible for setting up the test database. It is called during the setup of the test case. It uses the ParallelTesting
facade to handle the setup and teardown of the test database. It checks if the test database should be recreated or dropped, and takes the necessary actions.
ensureTestDatabaseExists
This method ensures that a test database exists and returns its name. It checks if a dummy table exists in the test database. If the table doesn't exist, it drops the test database if it already exists and creates a new one. It returns an array containing the name of the test database and a boolean indicating if the database was created.
ensureSchemaIsUpToDate
This method ensures that the current database test schema is up to date. It calls the migrate
artisan command to run any pending migrations. It sets a static flag to indicate that the schema is up to date.
usingDatabase
This method runs a given callback using the given database. It temporarily switches the database configuration to the specified database, executes the callback, and then switches the database configuration back to its original value.
whenNotUsingInMemoryDatabase
This method applies the given callback only when tests are not using an in-memory database. It checks if the database configuration is not set to an in-memory database like :memory:
, and if so, it calls the callback with the name of the database.
switchToDatabase
This method switches the database configuration to the given database. It purges the database connection, retrieves the default connection's URL or database name from the configuration, and updates the URL or database name with the given database.
testDatabase
This method returns the name of the test database. It appends a unique token generated by the ParallelTesting
facade to the database name to create a distinct test database for each test case.
<?php
namespace Illuminate\Testing\Concerns;
use Illuminate\Database\QueryException;
use Illuminate\Foundation\Testing;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\ParallelTesting;
use Illuminate\Support\Facades\Schema;
trait TestDatabases
{
/**
* Indicates if the test database schema is up to date.
*
* @var bool
*/
protected static $schemaIsUpToDate = false;
/**
* Boot a test database.
*
* @return void
*/
protected function bootTestDatabase()
{
ParallelTesting::setUpProcess(function () {
$this->whenNotUsingInMemoryDatabase(function ($database) {
if (ParallelTesting::option('recreate_databases')) {
Schema::dropDatabaseIfExists(
$this->testDatabase($database)
);
}
});
});
ParallelTesting::setUpTestCase(function ($testCase) {
$uses = array_flip(class_uses_recursive(get_class($testCase)));
$databaseTraits = [
Testing\DatabaseMigrations::class,
Testing\DatabaseTransactions::class,
Testing\DatabaseTruncation::class,
Testing\RefreshDatabase::class,
];
if (Arr::hasAny($uses, $databaseTraits) && ! ParallelTesting::option('without_databases')) {
$this->whenNotUsingInMemoryDatabase(function ($database) use ($uses) {
[$testDatabase, $created] = $this->ensureTestDatabaseExists($database);
$this->switchToDatabase($testDatabase);
if (isset($uses[Testing\DatabaseTransactions::class])) {
$this->ensureSchemaIsUpToDate();
}
if ($created) {
ParallelTesting::callSetUpTestDatabaseCallbacks($testDatabase);
}
});
}
});
ParallelTesting::tearDownProcess(function () {
$this->whenNotUsingInMemoryDatabase(function ($database) {
if (ParallelTesting::option('drop_databases')) {
Schema::dropDatabaseIfExists(
$this->testDatabase($database)
);
}
});
});
}
/**
* Ensure a test database exists and returns its name.
*
* @param string $database
* @return array
*/
protected function ensureTestDatabaseExists($database)
{
$testDatabase = $this->testDatabase($database);
try {
$this->usingDatabase($testDatabase, function () {
Schema::hasTable('dummy');
});
} catch (QueryException) {
$this->usingDatabase($database, function () use ($testDatabase) {
Schema::dropDatabaseIfExists($testDatabase);
Schema::createDatabase($testDatabase);
});
return [$testDatabase, true];
}
return [$testDatabase, false];
}
/**
* Ensure the current database test schema is up to date.
*
* @return void
*/
protected function ensureSchemaIsUpToDate()
{
if (! static::$schemaIsUpToDate) {
Artisan::call('migrate');
static::$schemaIsUpToDate = true;
}
}
/**
* Runs the given callable using the given database.
*
* @param string $database
* @param callable $callable
* @return void
*/
protected function usingDatabase($database, $callable)
{
$original = DB::getConfig('database');
try {
$this->switchToDatabase($database);
$callable();
} finally {
$this->switchToDatabase($original);
}
}
/**
* Apply the given callback when tests are not using in memory database.
*
* @param callable $callback
* @return void
*/
protected function whenNotUsingInMemoryDatabase($callback)
{
if (ParallelTesting::option('without_databases')) {
return;
}
$database = DB::getConfig('database');
if ($database !== ':memory:') {
$callback($database);
}
}
/**
* Switch to the given database.
*
* @param string $database
* @return void
*/
protected function switchToDatabase($database)
{
DB::purge();
$default = config('database.default');
$url = config("database.connections.{$default}.url");
if ($url) {
config()->set(
"database.connections.{$default}.url",
preg_replace('/^(.*)(\/[\w-]*)(\??.*)$/', "$1/{$database}$3", $url),
);
} else {
config()->set(
"database.connections.{$default}.database",
$database,
);
}
}
/**
* Returns the test database name.
*
* @return string
*/
protected function testDatabase($database)
{
$token = ParallelTesting::token();
return "{$database}_test_{$token}";
}
}