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

Testing Approach Overview0:00

So everyone knows you're supposed to test your apps, right? But which parts and how in-depth you should test them and at what point you start testing them, it's always a big question. Adam Wathen made a course about TDD for Laravel developers that's fantastic, but it also has led to a lot of people thinking TDD is the only true way. I personally believe that TDD is very helpful in certain settings and less helpful in other settings. So what I tend to do is what we've been doing here, which is something that is a little bit akin to what some people describe as spike and stabilize. Spike meaning you build the thing, don't really worry about it being perfect. And then stabilize, which is when you write tests and you refactor it and you get it to something you're actually proud of. There are also tons of conversations about what makes a feature test versus a unit test. And I would say the simplest thing is a unit test is something that really exists on its own. And a feature test is something that exists in a system. It's more like an integration test.

And I would say the simplest thing is a unit test is something that really exists on its own. And a feature test is something that exists in a system. It's more like an integration test. And that often helps me delineate whether I'm going to be TDDing or not. I tend to TDD unit tests, something that stands alone, often a php, a plain old PHP object or a POPO. Whereas with a feature test, I tend to build it because, you know, you have to see how the things interact. You might need to click around it in the browser and then you build the tests either when it's done or as I've mentioned in previous videos, there's often a moment where I say, let me change that. Now I have to go temporarily modify a select to even get the negative state. Or I have to go click through the thing three more times. That's really the moment where I go, wait a minute, this would be easier with the test.

Reviewing Breeze Tests1:26

Or I have to go click through the thing three more times. That's really the moment where I go, wait a minute, this would be easier with the test. And then I start building the test for it then. So let's take a look at what we get for free with Breeze. We can come over and we can see the example tests that come for free with a Laravel app. And they don't do anything other than just assert that your front page loads. But if you got a profile test and all these off things, this is starting to run assertions actually around the Breeze defined profile management. And then also the User aspects. So we've got authentication, email verification, everything that Breeze gives for us. We're going to have test coverage for all of those.

Testing Talks Listing1:56

So we've got authentication, email verification, everything that Breeze gives for us. We're going to have test coverage for all of those. So that's great. We don't have to build any test coverage around registration, login or anything else we get from Breeze, which means it's our job to test the things that we have already uniquely written for this project. The easiest thing for us to get started testing for any CRUD type application is a list page or a show page where there's no user interactions. We just want to make sure that if the database is in a certain state, that means the page shows in a certain state. So let's get started by making tests for our list talk and show talk page. All right, so we can say it lists talks on the list talks page. If you take a look at some of the other tests, you can see where they're creating a User and then acting as that User.

All right, so we can say it lists talks on the list talks page. If you take a look at some of the other tests, you can see where they're creating a User and then acting as that User. They are visiting certain pages. This is pudding, but you at least can understand we have the basic concept. It might be easier to take a look at the profile test to see what we've got when the profile page is displayed. So we have a User and then we visit a route and then we check that page to make sure it loaded. So let's start with that by default. We're going to, as a User, visit a different page. First of all, we just want to make sure that it works. So we've got it lists talks on the list talks page. We can do that.

First of all, we just want to make sure that it works. So we've got it lists talks on the talks index page. OK, but we don't actually have an assertion that we see a particular talk here. And that's one of the things we want to test to make sure we have. So let's make a Talk. And then assert that we see that talk title on our page. Now, if you're saying this test is going to fail, you're right. It's going to fail because we are looking at the talks page for the specific User. And this talk is not actually attached to our specific User that we're testing for.

It's going to fail because we are looking at the talks page for the specific User. And this talk is not actually attached to our specific User that we're testing for. So we need to make sure that this talk is attached to that User. There are a lot of different ways to do that. But the simplest one is to use has in the new factory stories. So you say has TalkFactory count 2 create. And you now have the talk attached. And you can grab the talk title by just saying user.talks()->first()-title. And now we're green because that talk is not in there. One of the ways we can continue that test that we sort of discovered right there.

And now we're green because that talk is not in there. One of the ways we can continue that test that we sort of discovered right there is by creating a talk that is not associated with this User. Assert don't see other User's talk title. So when that's green, we now know that our talk is going to show on the talks page and somebody else's talk is not going to show on the talks page. All right, so what else do we want to do? We talked about the show talks page. So we can say test it shows basic talk title. So we can say test it shows basic talk details on the talk show page.

So we can say test it shows basic talk title. So we can say test it shows basic talk details on the talk show page. And of course, you can also be making all sorts of other assertions to ensure that that list page has only certain ones. If you're doing pagination or it's showing other particular details, there's a lot of other assertions that you can do that are things like assertSeeText in order to make sure that one is before the other or all sorts of other potential things that you can test. But main thing I wanted to make sure is, hey, is my talk actually listed there? Yes. Great. Is somebody else's talk not listed there?

Testing Talk Show Page5:15

But main thing I wanted to make sure is, hey, is my talk actually listed there? Yes. Great. Is somebody else's talk not listed there? Okay, great. Once again, we need to build out a Talk, but this time we don't actually need to specifically visit it as our User. So we can actually just say $talk equals TalkFactory::create(). Great. And then we grab our $response here. We do want to make sure that it's okay, as always. And our $user here is just $talkAuthor.

We do want to make sure that it's okay, as always. And our User here is just talk author. So we can go to the index page for that specific talk. This is a great point to look at the fact that we actually probably want to use routes in these. So we want to call talks.show and then pass in talk. We can do the same thing up here. We can replace this right here with route('talks.index'). So we want to go to the show page for this specific talk, and we want to make sure that we actually load it correctly and it actually shows the talk title.

and we want to make sure that we actually load it correctly and it actually shows the talk title. Now, this didn't work, and it's a lovely moment where writing a test allows us to see that something we wrote that we thought was going to work, we never actually tested it in the live code. And so it would have sat there and tripped somebody up eventually. So this author relationship is based on this foreign ID, which we did not name author. We named user because that's the default right there. So if you want to name the relationship author,

We named User because that's the default right there. So if you want to name the relationship author, we have to pass in a second parameter here saying, yeah, it's called author, but the foreign key is actually user_id. So now it's working. So we now have an assertion that it lists all the talks in the talks.index page, that we can see our own talk information there. We can't see other people's talk information there. The route actually loads. We can go to a talks.show page,

The route actually loads. We can go to a talks.show page, and we can see the basic information about ours. We can also make sure that somebody else can't see our talks.show page. So we'll create our Talk. And then we'll create our other User. And then we'll make sure that if our other User tries to access it, they won't be able to. So let's run our test. And this is a great thing about writing tests like this.

So let's run our test. And this is a great thing about writing tests like this because they've discovered multiple things now for us where the thing doesn't actually work the way we're expecting. We're getting a 200 when we expect to get a 403, which means that user is actually going to be able to access it. So we're going to run our test. And we're going to get a 403. And we're going to get a 200 when we expect to get a 403. So we're going to run our test.

And we're going to get a 200 when we expect to get a 403. So we're going to run our test. And we're going to get a 200 when we expect to get a 403. Which means that User actually can access it. So let's go to talk.controller.at.show and realize there's no access control here. And if we had more time, we would build out more robust access control for this using a policy or at very minimum using a form request. For now, we're just going to do something real quick.

or at very minimum using a form request. For now, we're just going to do something real quick. We're going to say if talk.authorId is not equal to auth.userId abort forbidden. Quick note from Editor Matt, if instead of doing talk.arrow.authorId, which forces it to load the full author relationship, you've instead done talk.author_id, you have the ability to just pull the authorId property.

you've instead done talk.author.underscoreId, you have the ability to just pull the authorId property off of the talk without fully loading the author relationship. So if you were actually going to use this implementation long term, this would require one less database lookup. For now, I'll just keep it the way the original video was recorded. And you'll know better. So let's go back and check our tests. And there we go. Users can't see the talk show page for others' talks.

Testing Talk Creation9:01

And there we go. Users can't see the talk show page for others' talks. Now let's handle the more complex actions. We'll start with creating a Talk. And once again, it's often easiest to start with an existing test. Let's take a look at a PasswordUpdateTest. So you see we've got user, we're sending information over, and we're looking at the output. Sounds good. So what we're going to say is

Sounds good. So what we're going to say is User can create a Talk. Forgot to copy that, but it's okay. $user equals userFactory create. So acting as that user, we want to post to the talks.store method. And we want to figure out what parameters we want to pass in. One great test you can potentially write is

And we want to figure out what parameters we want to pass in. One great test you can potentially write is not pass in any parameters and say make sure it gives the errors you want. But for now we're going to say let's pass in just the required parameters and see if it works. And then there's two things we want to test here. And the first thing we want to test is that we're getting redirected to the page we want the users to end in. So that's sort of a user interface, user experience thing.

to the page we want the users to end in. So that's sort of a user interface, user experience thing. And we also want to make sure that the database is in the state we want it to be afterwards. So we know that the redirect should go back to the talks.index route. And we know we want an entry in the database. And there's a few different ways to do this. You can use the Eloquent model saying Talk::where('title', $title)

title is title count. And you can assert that that's one. You can also say this assert database has. And then say the talks table. And then say where title is title. You can really do whatever makes the most sense to you. The benefit of this is we're specifically naming it about a database assertion.

The benefit of this is we're specifically naming it about a database assertion. The downside of it is you're hard coding the table. So do whatever makes the most sense to you. But in general what we've been able to assert is that if you pass in these required fields you get redirected, you end up with a database. Now at this point you've got a lot of different directions you can go. You can choose in this moment to assert that users get certain errors when they pass in bad data. That's a pretty common test that people would write. You can assert that in that moment the session contains

when they pass in bad data. That's a pretty common test that people would write. You can assert that in that moment the session contains error state and that you get redirected back to the previous page. All of these are very valid and common things to do. But you at least want to start with this as your basic step. If I do the happy path, do I get the output that I want? The importance of some of those other options is based on how bad would it be if it didn't happen. I would say you definitely should build them the majority of the time. But they're a little bit less urgent when you're first writing your tests.

Testing Updates and Authorization11:57

I would say you definitely should build them the majority of the time. But they're a little bit less urgent when you're first writing your tests than the happy path works. But for the sake of time we're going to head over to create an UpdateTalkTest. So php artisan make:test UpdateTalkTest. Now that's very similar to what we were doing here. We're being a User, we're sending over a response and we're asserting that we have a certain outcome afterwards. So our first test here will be a User can update their talk.

So our first test here will be a User can update their Talk. And of course we always want the inverse or the security inverse at least which is a User cannot update another User's Talk. All right, so we've got this User but we really want this one as a Talk. And then we want that Talk's User or author to update their Talk.

or author to update their talk. So it'd be talks.update and let's say they want to change the title to new title here. So you can assert that it doesn't have errors. You can say something like assertSessionHasNoErrors. You can assert that you're redirected to a certain page. Let's see what we would be redirected to.

You can assert that you're redirected to a certain page. Let's see what we would be redirected to. We should be redirected to the talks.show page for that particular talk. We can assert the database has been updated. You can say assertDatabaseHas but you can also just say something like this assertEquals newTitle, talk->refresh()->title.

this assert equals new title here talk refresh title. In general what we want to do is we want to make sure that the update route works that the user interface works correctly and that the database is affected the way we want it to be. Right, because we require the type field even on updates. And there we go. So you can update it.

And there we go. So you can update it. So let's make sure somebody else can't update it. So we're going to say otherUser and say can they update it? New title here, new type. So we want to have errors and we want to make sure that the talk still has the old title.

and we want to make sure that the talk still has the old title. So we'll have to save that original title so we can run an assertion against it. And it looks like there's no errors. So let's turn this off and see what the outcome actually is. Yeah, it looks like we found another authorization issue here. Again, very glad we're doing this, right?

another authorization issue here. Again, very glad we're doing this, right? TalkController.update. And just like the show method we don't have any validation here to make sure that you actually own this. Once again, we'd probably use a policy but we can also just do a really simple one right here if we want. Say something like if talkAuthorId is not equal to authUserId abort 403. You also probably want to log it.

id abort 403. You also probably want to log it and other such fun things. Let's go over to our test and say does it work now? Of course not, because we didn't actually throw an authentication error back. We actually aborted so this would be assertForbidden. And there we go. You've gotten a forbidden response and the database stays the same way it was. I won't bore you with it, but we could also write the same tests.

a forbidden response and the database stays the same way it was. I won't bore you with it, but we could also write the same tests for deleting a Talk, making sure that I can delete my own Talk but somebody else can't delete my Talk. As you can see though, these tests are extremely valuable because they give us confidence that certain things can happen, which means it's protected from breaking later. But very often negative things can't happen. And that's one of the things that I really encourage people to do when you're first getting started testing is, especially if you have a pre-existing codebase

really encourage people to do when you're first getting started testing is, especially if you have a pre-existing codebase say, what would get you fired? Or what would get your company shut down or in the news for a bad reason? Start with testing there. Because we're building this from scratch, I also would say test your happy path. And if you go just those two, you've already made it really far down the road of having a solid set of test cases that really sets you up for success. So that's it for testing right now. Obviously we should write more tests as we go, but for the work we've done so far, those

So that's it for testing right now. Obviously we should write more tests as we go, but for the work we've done so far, those are the tests we want to cover. See you in the next one.

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