Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[4.x] Testing tenants is slow #250

Closed
antonkomarev opened this issue Dec 17, 2019 · 60 comments
Closed

[4.x] Testing tenants is slow #250

antonkomarev opened this issue Dec 17, 2019 · 60 comments
Assignees
Labels
feature New feature or request usability v4

Comments

@antonkomarev
Copy link
Contributor

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).

@stancl
Copy link
Member

stancl commented Dec 17, 2019

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.

@stancl stancl added this to the 2.3.0 milestone Dec 17, 2019
@drfraker
Copy link

@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

@stancl
Copy link
Member

stancl commented Dec 19, 2019

Hi @drfraker. Good idea, I'll take a look at your package alongside Laravel traits for some inspiration.

@drfraker
Copy link

@stancl I also like your idea of a trait that can be added onto test classes to bypass the tenant creation. Like a NoTenants trait. I've also seen where if you add certain keywords to a test name it will do the same thing for more granular control within a test class.

eg. test_it_can_do_something_no_tenants
Within the test case this would be picked up and not add tenants to that particular test. It's helpful when some tests in a class use tenants and others do not. A single trait would not be usable in that case.

@stancl
Copy link
Member

stancl commented Dec 19, 2019

to bypass the tenant creation

Just don't do it. Don't create any tenants and just run the tests. The use for that would be limited, though.

I think the opposite would be useful. A trait that creates & deletes the tenant before/after phpunit runs the tests. Any calls related to the current tenant (e.g. tenant-specific calls and inline tenant() calls) require a tenant to be created & identified.

And to test tenant creation using your signup form, you'd use no traits.

So you really only need two things:

  • a base Laravel TestCase for unit testing classes and feature testing tenant creation
  • a tenant-aware trait/TestCase that creates & identifies one tenant before the tests and deletes the tenant after the tests

@drfraker
Copy link

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 and tenant_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?

@stancl
Copy link
Member

stancl commented Dec 19, 2019

Hmm, I see. I'll take a deeper look at SnipeMigrations. Skipping the central & tenant DB migration/seeding process even on phpunit setup could speed things up considerably.

@stancl
Copy link
Member

stancl commented Dec 21, 2019

This might be related: #190 (comment)

@drfraker
Copy link

Hey @stancl I've been looking into how to make testing faster. I think a good start would be to have some flag that when set will tell the database manager that the database already exists and not to try to create one. One of the problems is that the database has to be created/migrated/deleted for every test case, which is slow. If the database does not have to be created we could presumably have a few databases set up for testing like tenant_1_test, tenant_2_test, ... and when creating test tenants simply set the _tenancy_database_name to tenant_1_test etc. If that existed, we could mimic the functionality of the RefreshDatabaseTrait on all tenant databases. Basically, that trait runs migrations and seeds once. Then sets the migrated state in RefreshDatabaseState::$migrated to true and starts a database transaction that rolls back once the test has run. On subsequent tests (when $migrated is true) it skips the migrations and just uses the transactions. If you could get a branch to the point where there was a setting to skip database creation I could probably fill in the rest of the pieces to make this work.

Does this make sense?

@stancl
Copy link
Member

stancl commented Jan 15, 2020

There are 3 types of tests:

  • central only - these test purely central functionality, completely separate from any tenancy things. These tests can use Laravel's testing traits.
  • central & tenant - tests that interact with both parts of the application, e.g. a signup form that creates tenants. The tenant creation is up to the test, so that part can't be made more performant, but we could at least store the central DB empty post-migration state in a transaction and roll back any changes to central DB after each test (this would delete tenants, domains, but only assuming the DB storage driver is used)
  • tenant only - these tests don't care about central anything, they run purely in the tenant app

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:

  • hold the state of an empty database seeded with one tenant and one domain for the central database. Roll back to that after every test
  • hold the state of an empty, post-migration tenant database in another transaction (on the tenant connection) and use that as the base for each test

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.

@stancl stancl added feature New feature or request usability and removed support labels Feb 1, 2020
@stancl stancl removed this from the 2.3.0 milestone Mar 15, 2020
@stancl
Copy link
Member

stancl commented Mar 15, 2020

To add Laravel 7 support faster, I'm putting this off for 2.4.0.

@colinmackinlay
Copy link

A technique that I'm going to try to adapt is this one that I used to really speed up tests with hyn/multitenant: https://medium.com/@sadnub/faster-multitenant-testing-in-laravel-4769eae4b603. It made a huge difference.

Before that though I need to figure out how to make my existing test work now that I've converted to stancl/tenancy :)

At the moment my problem is that when using a route from within a test e.g.

        $this->post(route('tenant.site.area.store'),
            ['name' => 'New Location'])
            ->assertRedirect(route('tenant.site.area.index'));

I get a 404 error because the url being called is:
https://host.test/site/area
where it should be:
https://tenant.host.test/site/area

The route works fine in the app but the test doesn't know it's in a tenant even though setup has:

        tenancy()->init('testing.host.test');

Any thoughts anyone?

@stancl
Copy link
Member

stancl commented Apr 25, 2020

You can specify the domain that the tenant should be redirected to if you enable the TenantRedirect feature.

return redirect()->route(...)->tenant($domain);

Otherwise the app has no way of knowing what domain to use, since tenants can have multiple domains.

@colinmackinlay
Copy link

Thanks for the response. Don't quite follow how that helps in the test. Certainly with that enabled I can get a redirect to a tenant page:

redirect()->route('tenant.site.area.store')->tenant('testing.host.test')

That works. But I can't post to a route modified by tenant which is what the test needs:

post(route('tenant.site.area.store')->tenant('testing.host.test'),['name' => 'New Location']);

Returns an error: Call to a member function tenant() on string

I would have thought the app/test could know which domain I had initialised from tenancy()->init('testing.host.test');

The app itself is fine because when it is live code the request is coming from the tenant domain. It's only the tests that fail, not the code.

@stancl
Copy link
Member

stancl commented Apr 25, 2020

redirect()->route() returns something different than route(). route() returns a string. You need tenant_route() for route strings w/ tenant domains swapped.

I would have thought the app/test could know which domain I had initialised from tenancy()->init('testing.host.test');

No, because testing.host.test is resolved to a tenant id, and each tenant can have an infinite amount of domains.

@colinmackinlay
Copy link

Thanks - tenant_route() sorted it, I can store the domain in the TenantTestCase and just modify all my tests.

Couldn't find anything in the documentation about the tenant_route() helper but got it in the helpers file.

All the work converting from hyn definitely seems to be worth it though. Great package :)

@devoncmather
Copy link

devoncmather commented May 25, 2020

Looks like this has been added to V3 roadmap 👍🏻

In the meantime has anyone had relative success in setting this up on their own to speed up tests? My current test suite takes about 15 mins to run when using sqlite, and on GitHub actions on a mysql db it takes anywhere between 1-5 hours if it doesn't time out first. It's gotten to the point where I would like to try to solve it using @stancl comment from 15 jan as an initial approach. Just wanted to reach out and see if anyone has already had any luck with a solution so far.

Cheers 😊

@stancl
Copy link
Member

stancl commented May 26, 2020

With v3 coming soon, I'd like to make this part of 3.0 or 3.1.

@okihus
Copy link

okihus commented Aug 12, 2020

Any updates on this one?

@devonmather
Copy link
Contributor

I have had luck using mysql databases. This is a stripped-down version of my test case, the brand model is a wrapper around the tenant this is for V2. The second brand created is used for a couple of edge cases (like making sure commands run for multiple tenants). There are caveats with this approach but if there is interest in this solution I can go into a bit more detail. Running 2000+ tests takes about 5 minutes, was close to an 1+ hours before this change.

abstract class TestCase extends BaseTestCase
{
    use CreatesApplication, RefreshDatabase;

    protected $brand;

    protected bool $tenancy = false;

    protected function setUp(): void
    {
        parent::setUp();

        config(['tenancy.database.prefix' => 'tenant_test_']);
        config(['tenancy.filesystem.suffix_base' => 'tenant_test']);

        if ($this->tenancy) {
            $this->initBrand();

            DB::beginTransaction();
        }
    }

    protected function tearDown(): void
    {
        if ($this->tenancy) {
            DB::rollback();
        }

        parent::tearDown();
    }

    public function initBrand()
    {
        $this->brand = Brand::first();

        Brand::init($this->brand->domain);
    }

    /**
     * Override the refresh trait for the conventional test database.
     *
     * @return void
     */
    protected function refreshTestDatabase()
    {
        config(['tenancy.storage_drivers.db.cache_ttl' => 60]);
        config(['tenancy.database.prefix' => 'tenant_test_']);
        config(['tenancy.filesystem.suffix_base' => 'tenant_test']);

        if (!RefreshDatabaseState::$migrated) {
            $this->artisan('migrate');
            $this->artisan('tenants:migrate');

            if (!Brand::first()) {
                factory(Brand::class)->create([
                    'subdomain' => 'tenant-test',
                ]);
            }
            if (!Brand::skip(1)->first()) {
                factory(Brand::class)->create([
                    'subdomain' => 'tenant-test-2',
                ]);
            }

            $this->app[Kernel::class]->setArtisan(null);

            RefreshDatabaseState::$migrated = true;
        }

        $this->beginDatabaseTransaction();
    }
}

@stancl
Copy link
Member

stancl commented Aug 12, 2020

Any updates on this one?

There's this: https://sponsors.tenancyforlaravel.com/frictionless-testing-setup

And I'll be adding database transactions to it soon.

@chrillep
Copy link

any update ? and maybe migrate to using LazilyRefreshDatabase

@lionslair
Copy link

Nope still slow. Tried a range of things too but it needs to migrate each run. Using on a legacy project and has a lot of migrations.

@ReinisL
Copy link

ReinisL commented Jul 6, 2022

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

@DanjBethel
Copy link

config(['tenancy.database.prefix' => 'tenant_test_']);
        config(['tenancy.filesystem.suffix_base' => 'tenant_test']);

Can you elaborate? Perhaps share some code?

@francoisauclair911
Copy link

@plakhin did you ever manage to get this to work with DatabaseMigrations, your solution works but it doesn't clear the tables within a test suite. So some of my assertions fail

@plakhin
Copy link

plakhin commented Aug 30, 2022

@francoisauclair911, I've managed to get database refreshing before each test, will post it here later, currently AFK. However it's not as fast as using transactions.

@plakhin
Copy link

plakhin commented Aug 31, 2022

@francoisauclair911.

trait RefreshDatabaseWithTenant
{
    use RefreshDatabase {
        beginDatabaseTransaction as parentBeginDatabaseTransaction;
    }
    ...
    protected static function addAfterClass()
    {
        app()->make('db')->connection()->disconnect();
        tenant()->delete();
    }
    ...
abstract class TestCase extends BaseTestCase
{
    ...
    public static function tearDownAfterClass(): void
    {
        if (method_exists(static::class, 'addAfterClass')) {
            method_exists(static::class, 'addAfterAll')
                ? (new static(fn () => null, '', []))->setUp()
                : (new static())->setUp();
            static::addAfterClass();
        }
        parent::tearDownAfterClass();
    }
    ...

@mreduar
Copy link

mreduar commented Aug 31, 2022

@francoisauclair911.

trait RefreshDatabaseWithTenant
{
    use RefreshDatabase {
        beginDatabaseTransaction as parentBeginDatabaseTransaction;
    }
    ...
    protected static function addAfterClass()
    {
        app()->make('db')->connection()->disconnect();
        tenant()->delete();
    }
    ...
abstract class TestCase extends BaseTestCase
{
    ...
    public static function tearDownAfterClass(): void
    {
        if (method_exists(static::class, 'addAfterClass')) {
            method_exists(static::class, 'addAfterAll')
                ? (new static(fn () => null, '', []))->setUp()
                : (new static())->setUp();
            static::addAfterClass();
        }
        parent::tearDownAfterClass();
    }
    ...

How long would you say it would reduce this Trait?

@plakhin
Copy link

plakhin commented Aug 31, 2022

It depends, just try in your project

@stancl
Copy link
Member

stancl commented Aug 31, 2022

I'll reopen this since testing is something we want to focus on in v4 👍🏻

@stancl stancl reopened this Aug 31, 2022
@stancl stancl changed the title Testing tenants is slow [4.x] Testing tenants is slow Aug 31, 2022
@stancl stancl removed this from the 2.4.0 milestone Aug 31, 2022
@mreduar
Copy link

mreduar commented Aug 31, 2022

I'll reopen this since testing is something we want to focus on in v4 👍🏻

How are things going with V4?

@stancl stancl added the v4 label Oct 1, 2022
@viicslen
Copy link
Contributor

viicslen commented Jan 27, 2023

Here's what I have been using so far and it has been working perfectly

trait WithTenancy
{
    protected function setUpTraits(): array
    {
        $uses = parent::setUpTraits();

        if (isset($uses[WithTenancy::class])) {
            $this->initializeTenancy($uses);
        }

        return $uses;
    }

    protected function initializeTenancy(array $uses): void
    {
        $organization = Organization::firstOr(static fn () => Organization::factory()->create());
        tenancy()->initialize($organization);

        if (isset($uses[DatabaseTransactions::class]) || isset($uses[RefreshDatabase::class])) {
            $this->beginTenantDatabaseTransaction();
        }

        if (isset($uses[DatabaseMigrations::class]) || isset($uses[RefreshDatabase::class])) {
            $this->beforeApplicationDestroyed(function () use ($organization) {
                $organization->delete();
            });
        }
    }

    public function beginTenantDatabaseTransaction(): void
    {
        $database = $this->app->make('db');

        $connection = $database->connection('tenant');
        $dispatcher = $connection->getEventDispatcher();

        $connection->unsetEventDispatcher();
        $connection->beginTransaction();
        $connection->setEventDispatcher($dispatcher);

        $this->beforeApplicationDestroyed(function () use ($database) {
            $connection = $database->connection('tenant');
            $dispatcher = $connection->getEventDispatcher();

            $connection->unsetEventDispatcher();
            $connection->rollBack();
            $connection->setEventDispatcher($dispatcher);
            $connection->disconnect();
        });
    }
}

Then on the tests that need it I just have to reference it like so:

PestPHP

use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\WithTenancy;

uses(WithTenancy::class);
uses(DatabaseTransactions::class);

// ...

PHPUnit

namespace Tests\Feature;

use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\TestCase;
use Tests\WithTenancy;

class ExampleTest extends TestCase
{
	use WithTenancy;
	use DatabaseTransactions;

	// ...
}

@mreduar
Copy link

mreduar commented Jan 27, 2023

@viicslen It looks very nice and elegant. But how is the speed? Did you have it in a different way before?

@viicslen
Copy link
Contributor

viicslen commented Jan 27, 2023

When used with DatabaseTransactions it's very fast (almost like without tenancy) since it only needs to create the tenant database once and then on every subsequent test, it just rolls back any database change done during the test

PS: I have also setup a custom version of the schema dump functionality for the tenant database

@mreduar
Copy link

mreduar commented Jan 27, 2023

When used with DatabaseTransactions it's very fast (almost like without tenancy) since it only needs to create the tenant database once and then on every subsequent test, it just rolls back any database change done during the test

PS: I have also setup a custom version of the schema dump functionality for the tenant database

For this to work, do I have to have the database already created and empty?

@stancl
Copy link
Member

stancl commented Feb 1, 2023

Fixed in v4

@stancl stancl closed this as completed Feb 1, 2023
@mreduar
Copy link

mreduar commented Feb 1, 2023

Fixed in v4

When will it be available to the general public?

@stancl
Copy link
Member

stancl commented Feb 1, 2023

There's no strict ETA yet. For now it's open in early access to sponsors. See #announcements on our Discord.

@dalholm
Copy link

dalholm commented Aug 19, 2023

@viicslen can you share a complete setup / config? Meaning Pest.php, TestCase, tenant aware test, and a central aware test?

@tjmugova
Copy link

tjmugova commented Jan 6, 2024

Here is what i have done with mine. I have heavy seeders that import large xmls so i cannot afford to keep migrating and seeding

<?php

namespace Tests;

use App\Models\Tenant;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\URL;

abstract class TestCase extends BaseTestCase
{
    use CreatesApplication;

    protected $tenancy = false;
    /**
     * If true, setup has run at least once.
     *
     * @var boolean
     */
    protected static $setUpRun = false;

    /**
     * Set up the test.
     */
    public function setUp(): void
    {
        parent::setUp();
        if (!static::$setUpRun) {
            Artisan::call('migrate:refresh', ['--seed' => true]);
            static::$setUpRun = true;
            config(['tenancy.database.prefix' => 'yopractice_testing_' . \Str::random() . '_']);
            //create test tenant
            if (!Tenant::count()) {
                $tenant = Tenant::create([
                    'name' => 'Test'
                ]);
                $tenant->createDomain("test");
            }
            //drop our test databases
        }
        if ($this->tenancy) {
            $this->initializeTenancy();
        }
    }

    public function initializeTenancy(): void
    {
        $tenant = Tenant::find(1);
        tenancy()->initialize($tenant);
        URL::forceRootUrl('http://' . $tenant->domains->first()->domain . '.' . config('app.central_domain'));
    }
}

@khalidmaquilang

This comment was marked as off-topic.

@citricguy
Copy link

citricguy commented Feb 26, 2024

In the event it helps someone, here is what I'm doing to test Central and Tenants. I'm using PEST, however it will work just fine with standard PHPUnit.

Database transactions are working for tenants so things are really fast, parallel testing is working, pest is working, code coverage, etc all work.

I'm using Laravel Sail, so you may need to adjust URL's, etc., for other environments.

Of course, make sure you have MySQL setup properly first: https://tenancyforlaravel.com/docs/v3/integrations/sail

Central Tests

<?php

uses(\Illuminate\Foundation\Testing\RefreshDatabase::class);

test('the admin login page returns a successful response', function () {
    $this->get(route('filament.admin.auth.login'))
        ->assertSuccessful();
});

Tenant Tests

<?php

uses(\Tests\Traits\RefreshDatabaseWithTenant::class);

test('the portal login page returns a successful response', function () {
    $this->get(route('filament.portal.auth.login'))
        ->assertSuccessful();
});

Here is my tests/Traits/RefreshDatabaseWithTenant.php. It is based on @plakhin and other's posts from this thread.

<?php

namespace Tests\Traits;

use App\Models\Tenant;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\ParallelTesting;
use Illuminate\Support\Facades\URL;

trait RefreshDatabaseWithTenant
{
    use RefreshDatabase {
        beginDatabaseTransaction as parentBeginDatabaseTransaction;
    }

    /**
     * The database connections that should have transactions.
     *
     * `null` is the default landlord connection, used for system-wide operations.
     * `tenant` is the tenant connection, specific to each tenant in the multi-tenant system.
     */
    protected array $connectionsToTransact = [null, 'tenant'];

    /**
     * We need to hook initialize tenancy _before_ we start the database
     * transaction, otherwise it cannot find the tenant connection.
     * This function initializes the tenant setup before starting a transaction.
     */
    public function beginDatabaseTransaction()
    {
        // Initialize tenant before beginning the database transaction.
        $this->initializeTenant();

        // Continue with the default database transaction setup.
        $this->parentBeginDatabaseTransaction();
    }

    /**
     * Initialize tenant for testing environment.
     * This function sets up a specific tenant for testing purposes.
     */
    public function initializeTenant()
    {
        // Hardcoded tenant ID for testing purposes.
        $tenantId = 'foo';

        // Retrieve or create the tenant with the given ID.
        $tenant = Tenant::firstOr(function () use ($tenantId) {

            /**
             * Set the tenant prefix to the parallel testing token.
             * This is necessary to avoid database collisions when running tests in parallel.
             */
            config(['tenancy.database.prefix' => config('tenancy.database.prefix') . ParallelTesting::token() . '_']);

            // Define the database name for the tenant.
            $dbName = config('tenancy.database.prefix') . $tenantId;

            // Drop the database if it already exists.
            DB::unprepared("DROP DATABASE IF EXISTS `{$dbName}`");

            // Create the tenant and associated domain if they don't exist.
            $t = Tenant::create(['id' => $tenantId]);
            if (!$t->domains()->count()) {
                $t->domains()->create(['domain' => $tenantId]);
            }

            return $t;
        });

        // Initialize tenancy for the current test.
        tenancy()->initialize($tenant);

        // Set the root URL for the current tenant.
        URL::forceRootUrl('http://foo.localhost');
    }
}

@mreduar
Copy link

mreduar commented Feb 26, 2024

@citricguy How long does a test normally take to initialize the tenant? I used your code and it takes about 8 to 10 seconds to complete a basic test.

@citricguy
Copy link

citricguy commented Feb 26, 2024

Maybe 0.5 to 1s for the trait initialization and then 0.05s to 0.2s per test after that in the same class. Running tests in parallel cut the time down a bit more. It is very similar to running tests without tenancy with DatabaseTransactions given that it is exactly the same concept.

I do a lot of TDD so slow tests freak me out. 🤣

This setup is nearly fully credited to @plakhin (#250 (comment))

Here are the results of a sail artisan test --profile on a new code base I'm playing with.
image

@karsvaniersel
Copy link

karsvaniersel commented Jun 26, 2024

The solution @citricguy provided works perfectly. However, I am using SQLite so I had to make some modifications.

For the people interested:

public function initializeTenant()
    {
        // Hardcoded tenant ID for testing purposes.
        $baseDomain = 'domain.test';
        $tenantId = 'pest';
        $tenantDomain = $tenantId.'.'.$baseDomain;

        // Retrieve or create the tenant with the given ID.
        $tenant = Tenant::firstOr(function () use ($tenantId, $tenantDomain) {

            /**
             * Set the tenant prefix to the parallel testing token.
             * This is necessary to avoid database collisions when running tests in parallel.
             */
            config(['tenancy.database.prefix' => config('tenancy.database.prefix').ParallelTesting::token().'_']);

            // Define the database name for the tenant.
            $dbName = config('tenancy.database.prefix').$tenantId;

            // Drop the database if it already exists.
            $dbPath = database_path("{$dbName}");

            // Check if the file exists and delete it
            if (File::exists($dbPath)) {
                File::delete($dbPath);
            }

            // Create the tenant and associated domain if they don't exist.
            $t = Tenant::create(['id' => $tenantId]);
            if (! $t->domains()->count()) {
                $t->domains()->create(['domain' => $tenantDomain]);
            }

            return $t;
        });

        // Initialize tenancy for the current test.
        tenancy()->initialize($tenant);

        // Set the root URL for the current tenant.
        URL::forceRootUrl("http://$tenantDomain");
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature New feature or request usability v4
Projects
None yet
Development

No branches or pull requests