Pest Setup Overview0:06
Now before we move on to our extensive final project, I wanna finish up with a review of pest PHP and browser testing, which is how I would recommend you get started in the testing world. Okay? So visit pest PHP dot com, come down to start testing and installation. And it's possible though, that the way you installed Laravel from the get go already includes pest. And I'll show you an example.
already includes pest. And I'll show you an example. If you were starting from scratch, you might say Laravel new example, you can add the pest flag and that's gonna use pest test, the default framework. All right? So if we cd into that project, we can now immediately run PHP Artisan test, which is using pest, uh, to run those tests. Alternatively, you can say vendor bin Pest Oregon. If vendor bin is in your path, you should just be able
Alternatively, you can say vendor bin Pest Oregon. If vendor bin is in your path, you should just be able to run pest. Okay? Finally, if I cat one of these tests real quick, uh, tests, what do we have here? Feature example test. Sure enough, we can see that it is using pest and we can see that syntax there. Okay, great. But otherwise, if you're installing this from scratch,
Okay, great. But otherwise, if you're installing this from scratch, then let's run these two commands. Looks good. Next we should initialize pest, and that's gonna give you a pest PHP file. It's gonna give you some examples. And in fact, uh, let's just delete these so we can start from scratch. So if we were to run this, yeah, you'll see that it gives you test slash pest PHP, a parent test case
Unit vs Feature Tests1:30
So if we were to run this, yeah, you'll see that it gives you test slash pest PHP, a parent test case that boots up the framework, and then two example tests that you might run. So you have feature and unit. If this is all new to you, I would ignore unit just at the beginning unit is very targeted, very, um, low level. When I instantiate this class and I call that method, then I expect that response.
When I instantiate this class and I call that method, then I expect that response. A feature test or a browser test is far more broad. So when I open the browser and I submit this form, then I should be signed in. Notice how much more broad that is, and we can call that a feature test or a browser or an acceptance test or kind of the same thing. Okay? So let's have a look at the feature test. And sure enough, we have an example.
Okay? So let's have a look at the feature test. And sure enough, we have an example. When we get or visit the homepage, we expect the response to be 200. So this is a perfect example because at the moment you may remember, we don't have a route set up for the homepage. So if I run this test using just the pest command, it's gonna fail, right? So already it's helping us out.
it's gonna fail, right? So already it's helping us out. It's saying, Hey, I tried to load the homepage and even that doesn't work. So you got problems bud. Uh, and we get a 4 0 4, okay? So of course if we wanna make this pass, we would go to our routes file and we would implement in your marketing page. You might do something like this, get the homepage, you could load a pages controller if you want.
You might do something like this, get the homepage, you could load a pages controller if you want. You could have a home controller. Uh, anything you need home marketing page for you to implement on your own. Okay? So now think about it. If we run the test again, we're gonna get green because now we are returning a 200, uh, response. Awesome, okay? But we're not done yet. So pests by default allows us
Installing Browser Testing3:12
Awesome, okay? But we're not done yet. So pests by default allows us to run some simple feature tests and some unit tests, but I also want dedicated browser tests that open up a physical browser so that I can interact with it. So if I switch back, if I scroll down, you'll see it has dedicated browser testing support. So that allows us to use visit instead of get, and again, that's literally opening a browser.
So that allows us to use visit instead of get, and again, that's literally opening a browser. And in this case, this means open the homepage and assert that somewhere on the page, you see, welcome. Okay, so let's get going on. Yeah, you can see some examples here where you can whip up a user, visit the page, click this button, fill out that form, submit it, and then assert that you were redirected somewhere. It's really incredibly powerful
and then assert that you were redirected somewhere. It's really incredibly powerful and it's such a great way to get started with testing. In my opinion. You're effectively reproducing the testing that you're already doing manually. All right, let's get going. So we're gonna require the plugin. It is a wrapper around a popular testing tool called playwrights, and then we call install.
around a popular testing tool called playwrights, and then we call install. Alright, all right, looks good. And then finally you'll see in the event that your test fails, which will happen almost immediately, uh, it will save a screenshot of what the browser looks like at that point in time. Uh, but you don't wanna commit that if you're using Git. So we wanna make sure we have this folder to our GI ignore. Let's do that. Now, let's go to our project GI Ignore
So we wanna make sure we have this folder to our GI ignore. Let's do that. Now, let's go to our project GI Ignore and yeah, maybe right here is fine. And while we're at it, if we haven't already, let's create that browser directory. Okay? So this is where we're gonna get started. I'm just gonna move this guy over here. We'll start with a simple browser test. Alright, so I'm gonna swap this out with visit the homepage
Alright, so I'm gonna swap this out with visit the homepage and assert that we see, um, welcome, which I don't think we will, right? So this will, this will fail the test. Let's run pest on test browser. Example test. And now you'll notice it's going to run this in headless mode, which means you don't see the browser, it's behind the scenes.
Fixing Browser Bootstrap5:16
which means you don't see the browser, it's behind the scenes. But if we scroll up, ooh, we get target class config does not exist. Okay? So this is just to configuration, uh, issue. It's not yet, uh, booting up the Laravel framework. Basically it's not bootstrapping the Laravel. We can fix that by going to pest PHP and let's make sure that pest extends test case. And you'll see real quick this, um, test case is responsible
and let's make sure that pest extends test case. And you'll see real quick this, um, test case is responsible for booting up the entire Laravel framework for the purposes of your test. But right now we're not extending from that within the browser folder. So now we will, any file within this folder is going to make sure that it extends from test case. Okay, next, I'm gonna un comment to this because I do wanna refresh my database for every test so
Okay, next, I'm gonna un comment to this because I do wanna refresh my database for every test so that I have a clean database, um, and, and there's no interference. For example, one test creates a user and now the second test already has one user. And you don't wanna do that. You always wanna start with a clean slate. Okay, let's run it again and with a little luck it takes a little longer
Okay, let's run it again and with a little luck it takes a little longer because we're actually opening a browser and it fails. So why did it take a little longer that time? Well, because we expected to see welcome and it's accounting for the fact that welcome might be coming in as a result of some kind of a Ajax request or some kind of delayed response. So you can configure this, but by default it waits four or five seconds just to see welcome.
So you can configure this, but by default it waits four or five seconds just to see welcome. Is it there? Is it there? Waiting, waiting, waiting. Nope, sorry. Didn't see welcome on the page and now it fails. Sure enough, it's not working. Alright, let's make it pass in the simplest way possible. Welcome to the homepage. Now we run it and we're gonna get, uh, a fairly quick green and it's passing.
Now we run it and we're gonna get, uh, a fairly quick green and it's passing. Okay? So check this out. Now I can also run this using the debug option so that we can see it in headed mode. Basically, I wanna look at the browser, but just keep in mind, it's still gonna be super, super fast. And yeah, did you see that? It is crazy fast. Probably too fast for the video and that's okay.
And yeah, did you see that? It is crazy fast. Probably too fast for the video and that's okay. But here's another option. If I switch back, I can add a call to this debug method. Okay? So now right at this point in the execution, it's gonna stop so that we can interact with the browser in its current state. Do not forget this method. I use it all the time. So if I were to switch back and just run it like normal now, there we go.
Testing User Registration7:45
So if I were to switch back and just run it like normal now, there we go. Now you can see it pauses and opens up a browser so we can interact with it. Perfect. Okay, so I think you get the basic idea. Why don't we write our first test? Maybe this will be related to authentication. So I will call it off and I'll say it registers a user, okay? So I recommend in situations like this, just in comments,
registers a user, okay? So I recommend in situations like this, just in comments, write out what needs to happen when I visit the registration page and I fill out and submit the form, then I should have an account and I should be signed in. Um, and I should be on the ideas page, right? That's generally the flow we would expect for the happy path.
That's generally the flow we would expect for the happy path. And remember in these tests, don't just do the happy path, do the failed validation path and assert that you see, uh, validation errors. Okay? So let's do it together. Visit the register page and if you, if you wanna send any check, always call that debug method to make sure is this correct? So you give it a run, excuse me, off test.
that debug method to make sure is this correct? So you give it a run, excuse me, off test. And there we go. I see my registration page, it's working. Okay, so now we're gonna fill out those inputs and here's how on the registration page, we can access these by their ID or their name or even a, a data js attribute or a data test attribute. So in this case, I wanna grab the input with the name of name and type a name. Then grab an input with the name of email and then password.
of name and type a name. Then grab an input with the name of email and then password. Okay? Very easy fill out name with Jane Dough. Next, let's copy this. Fill out email with jane@example.com. And then finally fill out password with, you know, Jane's password. 1, 2, 3. Finally, we're gonna press that register button and then I'm going to assert that the path is,
1, 2, 3. Finally, we're gonna press that register button and then I'm going to assert that the path is, uh, where do we go? We go to the ideas page after we are registered. Okay? So I think this probably isn't going to work the way you might expect, but let's have a look, give it a run. And anytime it takes a little bit of time, it means it's waiting for something that ain't gonna happen. So it fails and we can see, all right, well you expect it
it means it's waiting for something that ain't gonna happen. So it fails and we can see, all right, well you expect it to be on the Regi, I'm sorry you expect it to be on the ideas page, but it just loaded register. Okay? So let's work on this. Let's come back and before I hit register, I'm gonna debug and I'm gonna show you the problem. Okay? So here we are. It did successfully fill out the form and we expected it to press the register button, right? But notice there's a register button up here
and we expected it to press the register button, right? But notice there's a register button up here and we're dealing with a computer, right? So it saw, that's the first one on the page. Well, that's the one I'm gonna click register and now it reloads it and that's why we actually didn't get an account. Okay? So these are things you need to be aware of. You gotta be specific. Either use a unique button name
You gotta be specific. Either use a unique button name or um, add an identifier to target it directly rather than relying on the text of the button, which could potentially change. Okay? Here's how. Go to your registration page and right here, yeah, give it, uh, an id or if you ever have, uh, if you ever wanna add an identifier for the sole purpose of accessing it from your, your test, in those cases, I would recommend using this data
for the sole purpose of accessing it from your, your test, in those cases, I would recommend using this data test, uh, convention. Give it a name like register button. And now you can access this from your test. And it is crystal clear that this exists for the benefit of your test. Think about it. If you only did this, well six months from now, you might be like, you know what, we never access register button from our
well six months from now, you might be like, you know what, we never access register button from our CSS, we don't even use this. I can delete it, right? You're not even thinking of your test. But with this, it's crystal clear that register button is an identifier for my tests. Okay? Now I can reference that and I'll show you a little trick with a selector. You would normally do something like this.
and I'll show you a little trick with a selector. You would normally do something like this. You'd say like, give me the one where data test equals register button, right? But this is a little clunky, adding those brackets. So instead with pest, we can proceed it with an at symbol. And whenever you see that in your head, that's an indication that we are accessing a data, uh, js or a data test attributes. So register button, let's give it a run,
or a data test attributes. So register button, let's give it a run, cross our fingers and I hope we get green. And I'm sorry, I forgot to clear out debug one more time. There we go. Now we get green. It is in fact working. Okay, so now we can just fill this, uh, finish this up and say I should have an account. Okay, let's say expect user count to be one. We expect to have one user in our database.
to be one. We expect to have one user in our database. That would be fine. Next, I should be signed in. Well, you know that we can do things like this to grab the authenticated user, but you also have tests, um, helpers like this. You could say a third, a, a, a third, a third that I'm authenticated, um, in the app and that would work as well. And then finally, we already did this.
and that would work as well. And then finally, we already did this. So this looks pretty good to me. Let's give it a run and it works. Okay? So think about how powerful this is. Now you have a single command that will ensure that your registration form does in fact work the way you'd expect. It asserts that if the person provides the necessary details and they submit the form, then they,
It asserts that if the person provides the necessary details and they submit the form, then they, they should be authenticated. Uh, and we should have a new user in the database. And of course, if you want, you can even be a little more specific user where email is. Jane, does that exist in the database? I expect that to be true. Uh, the point is you can write these
Writing Ideas Browser Tests13:36
I expect that to be true. Uh, the point is you can write these however you want to come back, give it a run and that's gonna return green as well. Cool. Okay, so now I'm gonna get you started on the next thing I would want you to work on. If you're working along, I'd want you to create a new, uh, test for your ideas. So we'll call it idea test, okay?
test for your ideas. So we'll call it idea test, okay? It shows all ideas. This would, uh, assert that assuming you have some ideas, if you visit the slash ideas endpoint, then you should see them. Uh, let's add another one. It shows a single idea. Um, it shows an edit form to update an idea and you're just following credit at this point.
Um, it shows an edit form to update an idea and you're just following credit at this point. Um, make sure you also add the unhappy paths, like for auth test. What if it tries to register a user but they don't type in a valid email address? Or what if they type in an email address for a user who already exists? Should they receive some kind of feedback? Write those as tests.
Should they receive some kind of feedback? Write those as tests. So I'm gonna do the first one with you and then I'll leave the rest, uh, for you to work on, on your own. It shows all ideas. Okay? How about this? Given I'm signed in and I have one idea in the database, when I visit the ideas endpoint, I should see my one idea, right?
when I visit the ideas endpoint, I should see my one idea, right? That's what we wanna assert. Alright, let's sign in the user. And I can say this acting as, and let's create a user, user factory. Create, create a user, and acting as literally sets them as the authenticated user done. Next, I have one idea in the database. Cool.
as the authenticated user done. Next, I have one idea in the database. Cool. So let's do this. Here's our user. Let's say user ideas create and we can manually do that. Or don't forget, if you have a factory for an idea, you could quickly whip one up, um, in that way. So it looks like we haven't set this one up as a factory. Uh, so all you would have to do is use the has factory trait and then run the make factory command. We're not gonna bother with that though. Let's stay on task.
and then run the make factory command. We're not gonna bother with that though. Let's stay on task. It's not overly necessary in this case. Uh, an idea consists of a description, build a thing. All right, so now we have an idea in the database. Next when I visit ideas, all right, visit ideas. Finally I should see my one idea. All right? So assert, see, build a thing. That's it. And that's a perfectly fine test for you to write. Okay, let's give it a shot pest
And that's a perfectly fine test for you to write. Okay, let's give it a shot pest and let's just do this one directly, uh, idea test and it fails. Um, oh yes, of course. So we forgot about the state attribute. What, what state is the idea in? Is it an app pending state in progress completed? So this is actually a great opportunity for me to show you, uh, a couple things here.
So this is actually a great opportunity for me to show you, uh, a couple things here. First, of course, we can manually set it on the fly. So if we ran this, again, it passes, it shows all ideas works. However, you may find that there are situations where you always want to assume a default value, and if one is not provided, just use that. So another option is to omit this. And don't forget, once again, it's gonna fail, of course.
So another option is to omit this. And don't forget, once again, it's gonna fail, of course. Okay? We're gonna return to our idea model. And one thing you can do here is set, is set some initial attributes. So you can say the initial default attribute for state is always going to be pending. And now if I give this a run, even though I didn't provide it, it will assume that and it does pass.
even though I didn't provide it, it will assume that and it does pass. And also, um, it's beyond the scope of this video, but I would probably even make something like an idea state enum, um, that would consist of the, the valid states that I would support. So if we have in progress pending and completed, um, those would be the two, the three cases within my enum. And then I could reference that here,
the three cases within my enum. And then I could reference that here, doing something like idea state, um, pending or something like that. Okay, so if I switch back, I think you get the basic idea, right? You write out what it is you want to test, and then take each line, uh, in your, in your little outline and turn it into actual code. Create a user, sign them in, create an idea for them,
and turn it into actual code. Create a user, sign them in, create an idea for them, and then when they visit the ideas page, they should see that idea. So now if you're working along, I want you to implement these two on your own. And again, you're just playing around, create some more on your own, um, and, uh, assert that you can delete an idea if you have the proper permissions, and then figure out
and, uh, assert that you can delete an idea if you have the proper permissions, and then figure out what happens if you try to delete an idea but you don't have permission. What should happen in those situations? So once you feel comfortable with that in the next chapter, we're gonna start on the final project. We're gonna go pretty quick, but I think you're really going to enjoy it.
but I think you're really going to enjoy it.
