Faster Tests on “Tenancy for Laravel”
on Mutli-Database setups using Named SQLite Connections.
If you're like me, fast feedback loops in development are crucial for mentally unblocking creative flow, keeping development cadence and ensuring quality code is maintained.
Having to wait over 10-30 seconds for tests may not sound like much, however when testing over 300-400 times per development session, every performance improvement matters – especially when trying to remove bugs from your application.
In the Laravel ecosystem, Laravel has integrated an in-memory SQLite Driver to speed up testing when interacting with databases. All you should have to do is set your database connection to SQLite, and your database to :memory:
.
Unfortunately, when using Tenancy for Laravel, if you want to use multiple databases to separate out your tenants, this approach isn't supported, as the SQLite in-memory driver only supports a single database, quoting the reasoning below
Note: If you're using multi-database tenancy & the automatic mode, it's not possible to use:memory:
SQLite databases or theRefreshDatabase
trait due to the switching of default database.
The Guide to Faster Testing
To get around this limitation, we're going to leverage a tool called crhg/laravel-sqlite-named-memory-connection
, which supports multiple in-memory databases using the SQLite Driver.
https://github.com/crhg/laravel-sqlite-named-memory-connection/pull/5
Let's get started. Firstly, install the required package that will allow for a new database connection that does in fact support our multiple database requirement.
# Install the custom SQLite Named Memeory Connections repository for Laravel 9+
composer config repositories.laravel-sqlite-named-memory-connection vcs https://github.com/sifex/laravel-sqlite-named-memory-connection.git
# Install the package
composer require crhg/laravel-sqlite-named-memory-connection@dev-master --dev
Once installed, add your sqlite-named
driver to the databases.php
file, with the following configuration:
'connections' => [
...
'sqlite-named' => [
'driver' => 'sqlite-named',
'url' => env('DATABASE_URL'),
'database' => env('DB_DATABASE', database_path('database.sqlite')),
'prefix' => '',
'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
],
Create a new file called SQLiteInMemoryDatabaseManager.php
somewhere in your application. In this example, I used a folder called "App\Tenancy".
<?php
namespace App\Tenancy;
use Stancl\Tenancy\Contracts\TenantWithDatabase;
use Stancl\Tenancy\TenantDatabaseManagers\SQLiteDatabaseManager;
class SQLiteInMemoryDatabaseManager extends SQLiteDatabaseManager
{
public function createDatabase(TenantWithDatabase $tenant): bool
{
return true;
}
public function deleteDatabase(TenantWithDatabase $tenant): bool
{
return true;
}
public function databaseExists(string $name): bool
{
return false;
}
public function makeConnectionConfig(array $baseConfig, string $databaseName): array
{
$baseConfig['database'] = ':named-memory:'.database_path($databaseName);
return $baseConfig;
}
public function setConnection(string $connection): void
{
//
}
}
Update your Database Manager in tenancy.php
to now support sqlite-named
:
...
'database' => [
'managers' => [
'sqlite' => Stancl\Tenancy\TenantDatabaseManagers\SQLiteDatabaseManager::class,
'sqlite-named' => SQLiteInMemoryDatabaseManager::class, # Adding this
'mysql' => Stancl\Tenancy\TenantDatabaseManagers\MySQLDatabaseManager::class,
'pgsql' => Stancl\Tenancy\TenantDatabaseManagers\PostgreSQLDatabaseManager::class,
...
Edit your phpunit.xml
file to include the new connection, making sure replace "central
" with the expected name of your testing database.
<php>
...
<env name="DB_CONNECTION" value="sqlite-named"/>
<env name="DB_DATABASE" value=":named-memory:central"/>
...
</php>
Lastly, we need to supply a custom version of the RefreshDatabase
trait, that supports the deletion of the database. I've supplied a version called RefreshDatabaseWithTenant
here, but you may need to modify this for your own use.
Then in each test that interacts with your multi-tenancy, you can add the trait to your test file. Below is an example for one used in Pest
uses(\Tests\Traits\RefreshDatabaseWithTenant::class);
🎉 That should be it. You should now have faster tests in Tenancy for Laravel using In-Memory SQLite databases.
How much faster is it?
On Pest v3, we see that we're about 20% faster on non-parallelized tests, and around 41% faster on parallelized tests. When dealing with a larger size of codebase, this is a welcome change.
A better approach on the horizon
There does seem to be some awesome work on getting this operational in the future in version 4 of Tenancy for Laravel – as well as some work being published back to the Laravel core framework to make multiple in-memory SQLite databases supported.
I would love for this workaround to be officially supported, and definitely worth sponsoring to stanctl on Github if you haven't already.