How to change hard coded Eloquent $connection only in phpunit tests? How to change hard coded Eloquent $connection only in phpunit tests? laravel laravel

How to change hard coded Eloquent $connection only in phpunit tests?


Because you asked for Laravel magic... Here it goes. Probably an overkill and over engineered way.

Let's first create an interface whose sole purpose is to define a function that returns a connection string.

app/Connection.php

namespace App;interface Connection{    public function getConnection();}

Then let's create a concrete implementation that we can use in real world (production).

app/GlobalConnection.php

namespace App;class GlobalConnection implements Connection{    public function getConnection()    {        return 'global-connection';    }}

And also another implementation we can use in our tests.

app/TestingConnection.php (you can also put this in your tests directory, but make sure to change the namespace to the appropriate one)

namespace App;class TestingConnection implements Connection{    public function getConnection()    {        return 'testing-connection';    }}

Now let's go ahead and tell Laravel which concrete implementation we want to use by default. This can be done by going to the app/Providers/AppServiceProvider.php file and adding this bit in the register method.

app/Providers/AppServiceProvider.php

namespace App\Providers;use App\Connection;use App\GlobalConnection;// ...public function register(){    // ...    $this->app->bind(Connection::class, GlobalConnection::class);    // ...}

Let's use it in our model.

app/SomeModel.php

namespace App;use Illuminate\Database\Eloquent\Model;class SomeModel extends Model{    public function __construct(Connection $connection, $attributes = [])    {        parent::__construct($attributes);        $this->connection = $connection->getConnection();    }    // ...}

Almost there. Now in our tests, we can replace the GlobalConnection implementation with the TestingConnection implementation. Here is how.

tests/Feature/ExampleTest.php

namespace Tests\Feature;use Tests\TestCase;use App\Connection;use App\TestingConnection;class ExampleTest extends TestCase{    public function setUp(): void    {        parent::setUp();        $this->app->instance(Connection::class, TestingConnection::class);    }    /** @test */    public function your_test()    {        // $connection is 'testing-connection' in here    }}

Code is untested, but should work. You can also create a facade to access the method statically then use Mockery to mock the method call and return a desired connection string while in testing.


Unfortunately for me, none of these answers didn't do the trick because of my specific DB setup for multi tenancy. I had a little help and this is the right solution for this problem:

Create a custom class ConnectionResolver somewhere under tests/ directory in laravel

<?phpnamespace Tests;use Illuminate\Database\ConnectionResolverInterface;use Illuminate\Database\ConnectionResolver as IlluminateConnectionResolver;class ConnectionResolver extends IlluminateConnectionResolver{    protected $original;    protected $name;    public function __construct(ConnectionResolverInterface $original, string $name)    {        $this->original = $original;        $this->name = $name;    }    public function connection($name = null)    {        return $this->original->connection($this->name);    }    public function getDefaultConnection()    {        return $this->name;    }}

In test use it like this

create a method called create() inside tests/TestCase.php

protected function create($attributes = [], $model = '', $route = ''){    $this->withoutExceptionHandling();    $original = $model::getConnectionResolver();    $model::setConnectionResolver(new ConnectionResolver($original, 'testing'));    $response = $this->postJson($route, $attributes)->assertSuccessful();    $model = new $model;    $this->assertDatabaseHas('testing_db.'.$model->getTable(), $attributes);    $model::setConnectionResolver($original);    return $response;}

and in actual test you can simply do this:

/** @test */public function user_can_create_model(): void{    $attributes = [        'name' => 'Test Name',        'title' => 'Test Title',        'description' => 'Test Description',    ];    $model = Model::class;    $route = 'model_store_route';            $this->create($attributes, $model, $route);}

Note: that test method can have only one line when using setUp() method and $this-> notation

And that's it. What this does is forcing the custom connection name (which should be written inside config/database.php) and the model during that call will work with that connection no matter what you specify inside the model, therefore it will store the data into DB which you have specified in $model::setConnectionResolver(new ConnectionResolver($original, 'HERE'));


In the Eloquent Model you have the following method.

/** * Set the connection associated with the model. * * @param  string|null  $name * @return $this */public function setConnection($name){    $this->connection = $name;    return $this;}

So you can just do

$user = new User();$user->setConnection('connectionName')