Laravel 5: Handle multiple connections and testing
I'd rather not touch the production code and instead use the service container to create test-specific services.
In this case, if you want all your models to use the same default testing connection:
public function createApplication()
{
$app = require __DIR__.'/../bootstrap/app.php';
$app->make(Kernel::class)->bootstrap();
$fakeManager = new class ($app, $app['db.factory']) extends DatabaseManager {
public function connection($name = null) {
return parent::connection($this->getDefaultConnection());
}
};
$app->instance('db', $fakeManager);
Model::setConnectionResolver($fakeManager);
return $app;
}
(This overrides the CreatesApplication
trait, you could instead place this code anywhere between when the app is bootstrapped and when the migration command is called).
(Also note this is using PHP 7 inline anonymous classes. You could also define a fake db manager as a separate class).
As mentioned before, you first need to set the connection in each Model.
So, you setup the connections in the database config file, set the values in the .env
file and use these in the Model's constructors.
For testing, you could also do this.
Add the testing connection to the config/database.php
file and then use an overriding env file.
Create an additional env file, name it something like .env.testing
.
So, in your .env
file you will have:
CONNECTION_MYSQL=mysql
CONNECTION_POSTGRESS=postgress
Then in the .env.testing
file you can have:
CONNECTION_MYSQL=test_sqlite
CONNECTION_POSTGRESS=test_sqlite
Finally to load this env file when testing, go to CreatesApplication
trait and update to the following:
public function createApplication()
{
$app = require __DIR__.'/../bootstrap/app.php';
$app->loadEnvironmentFrom('.env.testing');
$app->make(Kernel::class)->bootstrap();
return $app;
}
By using the loadEnvironemtFrom()
method, all tests that use this trait will load the .env.testing
file and use the connections defined there.
Off the top of my head you could move the connection names to the .env file
in your model:
public function __construct(array $attributes = [])
{
$this->connection = env('MY_CONNECTION');
parent::__construct($attributes);
}
in your .env file
MY_CONNECTION=mysql
in phpunit.xml
<env name="MY_CONNECTION" value="sqlite"/>
Most answers are changing production code, which I don't like.
Since \Illuminate\Contracts\Foundation\Application
is available in your tests, let's use it!
<?php
declare(strict_types=1);
namespace Tests\Feature;
use Tests\TestCase;
use App\Models\Company;
class CompanyFeatureTest extends TestCase
{
/**
* @return void
*/
protected function setUp(): void
{
parent::setUp();
$this->app->bind(Company::class, function () {
return (new Company())->setConnection(config('database.default'));
});
}
}
Whenever your Company
class is called, we give back a manipulated one.
In this one we have changed the $connection
property.
If you have the following in your phpunit.xml
:
<server name="DB_CONNECTION" value="sqlite"/>
The value of config('database.default')
will be sqlite
.
More info about binding can be found here: https://laravel.com/docs/5.8/container#binding