تماشای این درس نیاز به اشتراک حرفه‌ای دارد.

Starting TenantScope tests0:00

Now that we've created our design, let's go ahead and add some tests. Some people who love to do TDD would have done this first, but it's okay to do your tests later. And we already basically wrote out the tests we need right here with this design, so we're just going to now implement these tests and make sure they pass. Let's start with the first one. So data should be segmented by tenant in the database. Let's go ahead and create a new test. We're going to call this the TenantScopeTest. Let's go to our app, and let's go down to our tests folder, and we can see here we have

We're going to call this the tenant scope test. Let's go to our app, and let's go down to our test folder, and we can see here we have a tenant scope test. Let's get rid of this example test and create our own. A model has a tenant_id on the migration. So before we get this first test going, let's make sure we have PHPUnit set up. I'm going to say this, assertTrue(true), and I want to make sure that this runs and will pass. So before I even do that, let's open up my PHPUnit, and if I go down to the bottom here, this is already set up.

Configuring PHPUnit with MySQL0:56

So before I even do that, let's open up my PHPUnit, and if I go down to the bottom here, this is already set up. A lot of people like this, but I actually like to change it up a bit. I'm going to make this MySQL, and instead of doing an in-memory database, I like to actually create a unit test database. So let's go to SQLPro. I'm just going to add a new, we'll call this TeamZPHPUnit. So we'll take that. I did this for two reasons. Number one, I use MySQL in production, so I actually want my tests to run in as close

I did this for two reasons. Number one, I use MySQL in production, so I actually want my tests to run in as close to a production-like environment as possible. And number two, I actually like to go into SQLPro and look at what's happening in my unit tests sometimes, and SQLPro doesn't access SQL-like databases. So let's go back to our test, and I like to add a couple things here. You've probably seen this before. We're going to use RefreshDatabase, and with Faker. And now we just need to see if this runs. If you're using PhpStorm like I am, you can just run your tests like this.

And now we just need to see if this runs. If you're using PhpStorm like I am, you can just run your tests like this. Now I do have to set this up because I haven't set this up before. It's really easy if you're using PhpStorm. I get this error. I'm just going to click Fix. I need to choose my language level, which is 7.4. In the interpreter, I just choose this. I'll apply. Everything else appears to be good, so let's hit Apply and Run.

Testing migration tenant_id2:10

I'll apply. Everything else appears to be good, so let's hit Apply and Run. And looks like my test passed. This is actually one of my favorite things about PhpStorm is it makes it so easy to get my tests running. Okay, so now we're going to get this going. Instead of asserting true, I want to run my artisan command. We're going to make a model. We'll call this a TestModel, and we want to add the migration. When we do this, I just want to find the migration file and check.

We'll call this a test model, and we want to add the migration. When we do this, I just want to find the migration file and check. It has a tenant ID on it. And then lastly, I want to clean this up because this will actually create this model in my real application. So I want to clean it up and make sure that that file, the model, and the migration are no longer in my project at the end of this test. So when we make a migration, let's go ahead and look at how those look. You're probably familiar, but we have all this date information at the beginning of a migration.

You're probably familiar, but we have all this date information at the beginning of a migration. So what we're going to have to do is actually grab the $timestamp. We're going to say $now equals now(), just before we run this. And then we're going to have to use this $timestamp to create this file name so that we can look for it. I've actually done that work, so let me just paste it in here. And so you can see I've created a file name that has the year, the month, the day, the hour, the minutes, and the seconds. And then I'm appending this create_tests_table.

hour, the minutes, and the seconds. And then I'm appending this create tests table. Now let's just assert true. We're going to use the file helper from the Facade. We want to check that it exists. So database_path, migrations, file name. Then once we're done, we want to clean this up. So we'll say file, delete, and we'll grab this here, paste it. And we also have to delete the actual model itself if we're going to rerun this. So we'll delete the model here.

And we also have to delete the actual model itself if we're going to rerun this. So we'll delete the model here. So now if we run this test, everything passes, but what we're not actually doing is checking that there's a tenantId in this migration. We're just checking that it was created. So let's go to our stub, and we're looking for this. And now that I'm reviewing this, I actually noticed one big thing that I failed to mention when we first created this is one of the most important things about building a single database multi-tenant application is you're going to want to add an index on your tenantId.

database multi-tenant application is you're going to want to add an index on your tenantId. Because since every query is scoped to that, it's going to be a lot better for your performance to have an index on that tenantId. So now in the test, we just want to make sure that the new migration actually includes this. So let's go back to our test. And right here, we'll just say this->assertStringContainsString. We're looking for this, which we just got from the stub. And we're looking in file_get, and again, let's grab this right here and put it there. So if we run our test one more time, everything is passing.

Testing tenant data visibility5:13

And we're looking in file, get, and again, let's grab this right here and put it there. So if we run our test one more time, everything is passing. So we have checked and made sure that our tenantId is actually on the migration when we create it. Okay, so now if we go back to this, this one's been done. And now we need to check if the user can only see data that belongs to his or her tenant. Now let's create our next test. A user can only see users in the same tenant. So let's create two tenants, tenantOne. And let's copy that.

So let's create two Tenants, tenantOne. And let's copy that. Paste it there, and we'll call this tenantTwo. Now let's create userOne. And in here, I want to make sure the tenantId is set to the first Tenant. And now let's do what we did in our first lesson. So let's create nine more. So we have 10 total in tenantOne. And then we'll do the same thing, we'll create 10. And those all live in tenantTwo.

And then we'll do the same thing, we'll create 10. And those all live in tenant2. So if we die and dump the $user, whoops, $userAccount, we should actually get 20. And so we have 20. But what we want to know is if we log in as this first User, that we would only see 10. So let's do that. Log in, $user1. And now let's assertEquals(10, $userCount). And if we run that test, everything passes. Okay, we're looking good.

Testing tenant-safe user creation7:04

And if we run that test, everything passes. Okay, we're looking good. We just need one more test that will test a User can only create a User in his tenant. So let's grab some of this stuff from up here. We will create a Tenant, we'll create a User. And then we do want to log in as $user1. Now we'll just create a User. This will be our $createdUser and it's a User class create. And after this is done, we just want to assert true. The $createdUser tenant ID is the same as $user1 tenant ID.

And after this is done, we just want to assert true. The created User tenant ID is the same as User one tenant ID. We run this test, everything passes. That's great. Now the only other thing we may want to do is, like I said, we don't even want people to add the wrong tenant and put that in here as well. So let's change this to say, even if other tenant is provided. I know this is a long test name, but I like it. So when we're creating this User, instead let's just go ahead and make a User so we don't persist it to the database yet.

So when we're creating this User, instead let's just go ahead and make a User so we don't persist it to the database yet. We'll change the tenantId. We'll say to tenantTwoId, and then let's just save it. Now again, what we're trying to show is because User one is in tenantOne, even if we try to put them in tenantTwo, it's not going to work. So let's go ahead and run this one. And looks like that one passed. So now let's run our full test suite just to make sure everything is passing. Go ahead and run.

Refactoring to BelongsToTenant trait8:54

So now let's run our full test suite just to make sure everything is passing. Go ahead and run. It's all green, so we're good. Now I think I'm ready to do a refactor. If we go to our User model, and let's close this out and take a look at this, we've got this booted function and we've put in the stub that we're always going to add this booted method, but it's not immediately clear, especially to a new developer, what is going on here. Now what I'd like to do is extract all of this functionality to a trait. So instead up here, I'm just going to say use Notifiable and BelongsToTenant. Now this obviously doesn't exist, so let's go ahead and create it.

So instead up here, I'm just going to say use Notifiable and belongsToTenant. Now this obviously doesn't exist, so let's go ahead and create it. We're going to go up into our app folder, let's create a new directory for traits. And we'll create a new PHP class for belongsToTenant. So this is not actually a class, we'll call this a trait. And if we go back to the User model, let's go all the way to the bottom. Let's just grab this whole thing, copy it and throw it right in here. We'll import the TenantScope. Now the only difference about using a trait is we don't want to use this booted method, we want to actually call it boot.

Now the only difference about using a trait is we don't want to use this booted method, we want to actually call it boot. And then we're going to use the name of the trait here. So if we do that, let's go back to the User model and delete this. Let's go up here, make sure we import this class. And if we run our test suite, everything is still passing. So that little refactor, pulling it into a trait now is working. Now lastly, we're going to want to update our stub to make sure that all the new models that are created use this trait. So let's go to our stubs, we'll go to the model.

that are created use this trait. So let's go to our stubs, we'll go to the model. And instead of this, we're just going to say use belongsToTenant. We can delete all of this, let's get rid of that. And lastly, we are going to import this and get rid of that. So I like this because I feel like it's really clean. If we go back to our User model, we just have this trait, it goes here, everything else is just normal on this model, every new model we create, we're going to have this trait. And the one thing is this is really it's describing an Eloquent relationship. So why don't we go in here and add one more thing.

And the one thing is this is really it's describing an Eloquent relationship. So why don't we go in here and add one more thing. Let's add an Eloquent relationship here. So it's for a Tenant. And we're just going to return this belongsTo Tenant class. And let's make sure we import this class. Let's rerun our test suite just to make sure everything's still working. Looks like it is. So now I'm really excited because we're ready, we're going to go start building our app. In the next lesson, we're going to start building the UI, adding some models and really making

So now I'm really excited because we're ready, we're going to go start building our app. In the next lesson, we're going to start building the UI, adding some models and really making this app functional.

دوست دارید گاهی خبرهای Laracasts را ایمیل کنیم؟