tenancy: [4.x] Testing tenants is slow
Accordingly to the documentation testing of tenants could be done this way:
<?php
namespace Tests;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
abstract class TestCase extends BaseTestCase
{
use CreatesApplication;
protected $tenancy = false;
public function setUp(): void
{
if ($this->tenancy) {
$this->initializeTenancy();
}
}
public function initializeTenancy($domain = 'test.localhost')
{
tenancy()->create($domain);
tenancy()->init($domain);
}
public function tearDown(): void
{
config([
'tenancy.queue_database_deletion' => false,
'tenancy.delete_database_after_tenant_deletion' => true,
]);
tenancy()->all()->each->delete();
parent::tearDown();
}
}
But if there is a lot of tests - it will be very slow. In my case ~3 seconds for each test with more than 1k tests (~50 minutes).
About this issue
- Original URL
- State: closed
- Created 5 years ago
- Reactions: 7
- Comments: 59 (23 by maintainers)
I’ve been able to use Parallel Testing with
php artisan test -p --recreate-databases
However, for some tests
DatabaseMigrations
Trait will be needed instead ofRefreshDatabase
. As soon as I need it and get it ready, will also post here.UPD: Also adding this container to
docker-compose.yml
speeded up tests significantly (I use Laravel Sail)Please note, that you’ll also need this update
DB_HOST=mysqlt
in `.env.testing’.Yeah, it’s on my roadmap to make a few traits like those provided by Laravel. (e.g. to only run migrations on phpunit setup rather than every single test setup).
There could be a trait for tests that don’t need to know about tenant creation (because they don’t test the central app) and only test parts of the tenant app. That trait could create a tenant & DB on phpunit setup and then cleanup after all tests are run.
I was running into this issue as well. I’ve made a slightly adapted
RefreshDatabase
trait that works for me. Tests were previously running for more than a second each, with this trait it was reduced back to 300ms (as it was before using multi-database tenancy).@viicslen can you share a complete setup / config? Meaning Pest.php, TestCase, tenant aware test, and a central aware test?
I’ll reopen this since testing is something we want to focus on in v4 👍🏻
In my case, I was able to run tests much faster using schema:dump command. To create a tenant schema dump you can run -
artisan tenants:run schema:dump
There are 3 types of tests:
The third category is what I expect to be the biggest group of tests for all apps that use this package, so let’s focus on that.
Assuming the DB storage driver is used, we can:
Later we can do something like Snipe migrations for even faster tests, but this is a good start.
I will have to look into how the Laravel migration traits work, because Application state is not persisted between tests (for obvious reasons), but the transaction persists. My concern is that Application being destroyed between tests could also destroy the tenant connection, which would probably break the transaction. But that shouldn’t be a big issue, if
RefreshDatabase
works, we should be able to make a similar trait.Ended up writing my own static method that creates a test tenant once when the first tenant-related test is run. Importantly, this means only one tenant is created.
Additionally, to help with cleanup, this method also keeps track of how many tests need to run (
phpunit --list-tests | grep '-' | wc -l
), and what the current test is. Then on the last test, it deletes the test tenant (which also triggers the jobs for deleting S3 bucket and Stripe customer).The trade-off of speed vs. testing “best practice” (i.e. creating a fresh environment/application for each test) is very clear here. The code I use is super specific to my use case, but happy to answer questions on how it works.
If there’s enough interest, I might be able to package it up or create a PR to include as a testing helper trait.
Here’s what I have been using so far and it has been working perfectly
Then on the tests that need it I just have to reference it like so:
PestPHP
PHPUnit
Seems like a very clean implementation, thanks a lot Erik! I’ll test it when I start working on v4.
BTW, did someone find a way to use
php artisan schema:dump
with multi-database multi-tenancy?With v3 coming soon, I’d like to make this part of 3.0 or 3.1.
Actually I was thinking about it a bit differently. My thought was to have 1 or more databases for testing tenants. Maybe like
tenant_1_db
andtenant_2_db
. These would be set up beforehand by the developer. Like SnipeMigrations, it will run the migrations once then take a snapshot of the databases in the “clean” state. After initial db intialization by importing the snapshot, the databases would use database transactions for each test so that no actual data gets saved to the test tenant databases. If a tenant migration file changes the app would know about it, re-run the tenant migrations and take a new mysql snapshot for future imports. The “central” database would be pre-seeded with the tenants belonging to the 2 pre-created databases, using custom db names. Snipe already allows a seeder to run before the snapshot is taken.This way we can avoid the slow part which is database creation and running migrations. Of course if a developer needed more than the pre-provisioned number of tenants for testing they could create a new one in the test. Since this isn’t a requirement most of the time the tests should run really fast.
Is there anything that would prevent us from going in this direction?
When will it be available to the general public?
It depends, just try in your project
any update ? and maybe migrate to using LazilyRefreshDatabase
@stancl I wrote a package the hijacks the Refresh Database trait. I’m not sure, but you may be able to find some useful code in there to make this happen. If I get some free time in the upcoming weeks I might just add it myself. This is a major pain point for us right now too.
https://laravel-news.com/snipe-migrations-laravel-package