GithubHelp home page GithubHelp logo

Testing Issue about laravel-multitenancy HOT 30 CLOSED

spatie avatar spatie commented on July 28, 2024 2
Testing Issue

from laravel-multitenancy.

Comments (30)

masterix21 avatar masterix21 commented on July 28, 2024 2

@sausin, I found a great solution.

Try to use the following TestCase.php:

namespace Tests;

use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
use Illuminate\Support\Facades\Event;
use Spatie\Multitenancy\Concerns\UsesMultitenancyConfig;
use Spatie\Multitenancy\Events\MadeTenantCurrentEvent;

abstract class TestCase extends BaseTestCase
{
    use CreatesApplication, DatabaseTransactions, UsesMultitenancyConfig;

    protected function connectionsToTransact()
    {
        return [
            $this->landlordDatabaseConnectionName(),
            $this->tenantDatabaseConnectionName(),
        ];
    }

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

        Event::listen(MadeTenantCurrentEvent::class, function () {
            $this->beginDatabaseTransaction();
        });
    }
}

I have already tried, and seems that works very well. If you confirm it, we will upgrade the documentation.

Thanks

from laravel-multitenancy.

sausin avatar sausin commented on July 28, 2024 1

@masterix21 Not sure I understand correctly, but manually specifying the transaction in each test could work but then I would need to update all of my tests (1000+). Also, the default connection was specified as the tenant connection. Changing it to the landlord connection makes no difference either.

Also the issue from laravel framework linked above indicates that specifying the connection to transact should work and is in fact 'working' but the transactions are being counted on the incorrect connection so the end result is not as expected.

If you meant something else, please let me know!

EDIT:- If you were looking for the output from the command, I get a list of the tables from the tenant database correctly. I had to change the command for sqlite DB::connection('tenant_testing_connection')->select(DB::raw("SELECT name FROM sqlite_master WHERE type='table'"))

from laravel-multitenancy.

sausin avatar sausin commented on July 28, 2024 1

@masterix21 I've already tried this, as mentioned above, with no success

from laravel-multitenancy.

masterix21 avatar masterix21 commented on July 28, 2024 1

Ok, now is better to close the issue, because for me has been solved. If you need more help for it, please reply anyway to it.

Thank you.

from laravel-multitenancy.

freekmurze avatar freekmurze commented on July 28, 2024 1

Feel free to send PRs to the docs. Each page contains a edit button that allows you to create a PR quickly 👍

from laravel-multitenancy.

masterix21 avatar masterix21 commented on July 28, 2024

hi @sausin, can you post a test example, please?

from laravel-multitenancy.

sausin avatar sausin commented on July 28, 2024

Sure! Please see below an example test on the reset route:

<?php

namespace Tests\Feature\Controllers\Auth;

use App\User;
use Illuminate\Auth\Notifications\ResetPassword;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Notification;
use Illuminate\Support\Str;
use Tests\TestCase;

class ForgotPasswordControllerTest extends TestCase
{
    use DatabaseTransactions;

    /** @test */
    public function it_resends_verification_email()
    {
        Event::fake();
        Notification::fake();

        $u = User::create([
            'name' => Str::random(10),
            'email' => $e = Str::random(10).'@example.com',
            'phone_number' => 00001112345,
            'password' => Hash::make('password'),
        ]);

        $this->postJson('/password/email', ['email' => $e])
            ->assertOk();

        Notification::assertSentTo($u, ResetPassword::class);
    }
}

I have a unique constraint on the phone_number field. The test runs fine the first time (as there is no entry for the above phone_number. Running it again, it throws up errors that the unique constraint got violated (because the previous run did not end in rollback of the transaction).

The test was running fine before (hundreds of times).

A simple workaround in the above example would be to use a random number on the field, but that wouldn't solve the issue that the DatabaseTransactions features aren't functioning.

Thanks!

from laravel-multitenancy.

sausin avatar sausin commented on July 28, 2024

Just to add some info to the discussion, I did some more digging and investigations are listed below:

  • the beginDatabaseTransaction method in the DatabaseTransactions trait is not able to get results from connectionsToTransact. In the setup listed above, the connections array is empty

  • Adding this protected $connectionsToTransact = ['tenant_testing_connection']; to the TestCase.php provides the trait method the necessary info, but it makes no difference. The transaction feature still doesn't work

EDIT:- Using the array should have worked. So why doesn't it 😬 ?

from laravel-multitenancy.

sausin avatar sausin commented on July 28, 2024

Okay, so diving into the codebase of laravel, I think I have located the issue. The transactions are actually being reported corresponding to the landlord database and not the tenant database.

Got this by looking at the workings inside the rollBack method in ManagesTransactions class. I am guessing that something is happening in this package which is messing up the database reporting.

from laravel-multitenancy.

masterix21 avatar masterix21 commented on July 28, 2024

@sausin DB facade use your default connection, but nothing prohibit to specify a connection:

DB::connection('tenant')->transaction(function () {
	return DB::connection('tenant')->select(DB::raw('SHOW TABLES'));	
});

Try and tell me if it works.

from laravel-multitenancy.

masterix21 avatar masterix21 commented on July 28, 2024

If you need to use a tenant connection as default, use "tenant" as the default connection. See the PR: #59.

We are talking with @freekmurze from many weeks, but there are a lot of things that don't convince him and others that are uncleared for me.

from laravel-multitenancy.

sausin avatar sausin commented on July 28, 2024

@masterix21 Using the tenant as default connection doesn't change anything on this issue unfortunately. It would definitely be awesome to get some thoughts on this from @freekmurze

from laravel-multitenancy.

masterix21 avatar masterix21 commented on July 28, 2024

@sausin isn't simple to help somebody without the code (your test code above said nothing of your problem), anyway I hope that @freekmurze could help you.

Have you already tried to use RefreshDatabase instead of DatabaseTransactions? It seems that DatabaseTransactions has been removed from Laravel documentation (but it's available in the API).

In the meanwhile, try to extend your TestCase (for the tenant) with:

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

        DB::connection('tenant')->beginTransaction();
    }

    protected function tearDown(): void
    {
        DB::connection('tenant')->rollBack();

        parent::tearDown();
    }

from laravel-multitenancy.

sausin avatar sausin commented on July 28, 2024

@masterix21 Indeed, the recommended way is to use RefreshDatabase in the docs. However, for my test setup DatabaseTransactions is exactly what I need to use. Under the hood, RefreshDatabase (as I understand it) makes a choice between using DatabaseTransactions or DatabaseMigrations.

Thanks for the tip above! I'm trying it with some mixed results right now. Will report back 👍

from laravel-multitenancy.

sausin avatar sausin commented on July 28, 2024

@masterix21 the above tip was definitely an improvement, thanks! Tests seem to be working when run in isolation but failing when run in a batch. Wow, this is just 😢

Edit: I am thinking this could be related to some weird behavior on the database connection

from laravel-multitenancy.

masterix21 avatar masterix21 commented on July 28, 2024

@sausin, great. Do you have a full example to understand if it depends on the spatie/laravel-multitenancy package? If you can, please create a repository on GitHub with a testable project to understand better: it should help us. Thanks.

from laravel-multitenancy.

sausin avatar sausin commented on July 28, 2024

@masterix21 I'll try to setup a repo with tests

from laravel-multitenancy.

masterix21 avatar masterix21 commented on July 28, 2024

@sausin, I have an update for the past problem. If you see the DatabaseTransactions trait, it has:

protected function connectionsToTransact()
{
    return property_exists($this, 'connectionsToTransact')
               ? $this->connectionsToTransact : [null];
}

It means that if you specify a property called connectionsToTransact in your test, the trait will begin a transaction for all connections of array:

protected $connectionsToTransact = [
    'landlord',
    'tenant',
];

Had you already tried it before using setUp() and tearDown()?

from laravel-multitenancy.

masterix21 avatar masterix21 commented on July 28, 2024

Sorry, you're right

from laravel-multitenancy.

sausin avatar sausin commented on July 28, 2024

The consequently observed behaviour is what's really strange. I don't understand how that's happening.

I've also hit some random bugs which make no sense to me (and as I mentioned above, most of the relevant tests just pass when run independently).

Trying to setup a repo so that the discussion can be easier 👍

from laravel-multitenancy.

masterix21 avatar masterix21 commented on July 28, 2024

Ok, I have simulated all your examples, and now I'm synced with you.

  • the beginDatabaseTransaction method in the DatabaseTransactions trait is not able to get results from connectionsToTransact. In the setup listed above, the connections array is empty

Yes, you're right. There are no nicely solutions at the moment. The only way seems to be to create a copy of DatabaseTransactions trait with a new name (for example TenantAwareDatabaseTransactions) to call your $this->beginDatabaseTransaction() as follow:

    use TenantAwareDatabaseTransactions;

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

        Tenant::first()->makeCurrent();

        $this->beginDatabaseTransaction();
    }

from laravel-multitenancy.

freekmurze avatar freekmurze commented on July 28, 2024

Closing this, as it seems to have been answered.

from laravel-multitenancy.

sausin avatar sausin commented on July 28, 2024

@freekmurze We've not been able to resolve the issue being noted in the comment above.

Also, even when the manually specifying the transactions in the TestCase class (thanks @masterix21 for the tip), the tests haven't reached passing stage (unlike before when they were all passing even when run randomly across many runs).

from laravel-multitenancy.

sausin avatar sausin commented on July 28, 2024

@masterix21 This setup is great 😃 and simplifies things quite a bit. I just had to add Tenant::first()->makeCurrent(); in the setUp method at the end for things to work. Another thing that has been required is an overridden middleware class:

<?php

namespace App\Http\Middleware;

use Closure;
use Spatie\Multitenancy\Http\Middleware\EnsureValidTenantSession as MiddlewareEnsureValidTenantSession;

class EnsureValidTenantSession extends MiddlewareEnsureValidTenantSession
{
    public function handle($request, Closure $next)
    {
        if (app()->environment() === 'testing') {
            return $next($request);
        } else {
            return parent::handle($request, $next);
        }
    }
}

I still have some weird errors. Setting up the test repo is long overdue (my bad 😞). I'll try to do it asap

from laravel-multitenancy.

sausin avatar sausin commented on July 28, 2024

Yes sure, thanks a lot for all the help @masterix21

from laravel-multitenancy.

sausin avatar sausin commented on July 28, 2024

@masterix21 So the last set of issues pending for me involve dispatch. I have a model App\SomeModel which has observer on creating, created and updating events.

On the created event, I am dispatching a job which takes the model as input. This setup works fine in reality (using the web interface of the app) but the tests fail with this error

Illuminate\Database\Eloquent\ModelNotFoundException: No query results for model [App\SomeModel].

The created method looks like this:

public function created(SomeModel $someModel): void
{
    dispatch(new DoSomethingWithNewModel($someModel))->onQueue('do-something');
}

If I comment out the dispatch line, the tests work fine.

Appreciate the help 😃

from laravel-multitenancy.

ArtisanTinkerer avatar ArtisanTinkerer commented on July 28, 2024

I was also experiencing this problem.

A quick test of the solution provided by @masterix21 is looking good, nice one.

Definitely would be good to have a testing section in the docs.

from laravel-multitenancy.

sausin avatar sausin commented on July 28, 2024

@freekmurze PR #101 is in place

from laravel-multitenancy.

masterix21 avatar masterix21 commented on July 28, 2024

Thank you

from laravel-multitenancy.

sausin avatar sausin commented on July 28, 2024

Least I could've done 👍

from laravel-multitenancy.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.