Laravel 5.8 – From The Ground Up - Laravel 5.8 Tutorial From Scratch – e49 – Testing 101 Using PHPUnit

Education, Programming

Laravel 5.8 – From The Ground Up

52 Lessons

Laravel 5.8 Tutorial From Scratch – e49 – Testing 101 Using PHPUnit

in this episode we're going to start to tackle the basics of unit testing or feature testing your application for some of you this may be an entirely

foreign concept we're gonna cover the basics of it and it does take some time to get into the workflow of actually testing your application so in a normal flow you would normally write some code

and then you manually test that it does what you think that it's supposed to do this is what we would consider basically manual testing would it be great if you could write tests for your entire

application that tested every single feature from top to bottom and then you can automate it and just run that over and over as you're changing code making sure that you haven't broken something

else I mean how many times have you made a change and then what's something that seemed completely unrelated bricks and you're not quite sure why if you change this one thing why something on the

other side of the application broke well of course everything has consequences but when we are manually testing it's a very focused kind of testing but we are only really concerned with what we are

changing however when you change your code that are repercussions and so because of that it could damage another part of your application and this is where testing really shines because

testing because it's automated it can run through every single test and test everything in your application in a single shot which is something that is completely impossible to do in a manual

way so if you've been following along the entire time with this course then you do know that we installed telescope if you installed telescope in your project telescope needs to be disabled

before we can actually run this so these next couple of steps are only for those of you that have telescope installed so let me jump into the terminal and let's run vendor bin slash PHP unit this will

run PHP unit now out of the box PHP unit is set up with level and PHP unit is the library that we're gonna use for testing our application however if you run this and you have telescope it will not work

it will fail and the reason for that is due to recent changes with the environment file and what we really need to do is just disable it for testing you really don't need telescope at all for

testing so let's go back to phpstorm again this is only if you have this installed in your machine so in the root directory let's go to a file called PHP unit XML and let's add a new line here

for telescope underscore enabled and let's set that to false hit save then go back and let's run vendor bin PHP again and if you get green great we are good to go so it says that it ran two tests

and it says that it had two assertions assertions are when you are basically comparing what you expect something to be against what your application gave you back for example you may say well if

I do this then I expect to see this particular record in the database so we would say that's an assertion it's just a word for saying that you made sure that something was true or equal or null

or whatever the comparison is that was true so that is an assertion now each test is just a function let me show you let's go back to phpstorm and let's go into our test directory now

this is the nice thing with letter well everything comes set up out of the box so inside tests you do have a features folder and a unit folder for now we're just gonna worry about the features

directory let's open up this example test dot PHP so this right here is a test and the way a test is written is that it either needs to start with the word test or it needs to have a special

annotation up here at the top something like this when you have this special annotation then you can just write whatever method name you want now me personally I do like my method names

basically being very large very verbose because you really do want to know what that test is supposed to be testing so something like test one that's not good enough for a

testing it needs to be something like a new user gets an email when it registers that is a proper function name for a test anything that is not descriptive of what that test does is not good enough

for testing so just make sure you have nicely written tests so we have this example test it doesn't really do anything but we do see that it tries to do a get request to slash so basically

our home page and it just makes sure that we get a status of 200 let's go ahead and change this file up I want to put one of our controllers under a test so let's work through this together

let's open up our HTTP and the controller that I'm thinking of is my customer controller obviously my customer control is nice and clean and everything in here I believe right now

does everything that it's supposed to do so this is what we would call backfilling the tests typically you would write the test alongside with your actual implementation but because we

were not talking about testing we're gonna add the back fill a test for the customers controller so I want to write a couple of tests to test what customers controller is actually doing so let me

go ahead and rename this test right here let's say refactor rename just name it customers test all right so we'll go ahead and rename that let's get rid of all of this right here and let's start a

new test all right so what would be a typical test that we actually run in our application well how about this if I click on customer list I want to make sure that if I'm not logged in that we

are redirected to the login page so let's go ahead and work through this to see how we can make that work so let's write a new test for only logged in users can see the customers list how

about that so that is one test that we need to make sure we need to make sure that if somebody tries to fetch that page we need to make sure that they are redirected so we'll say response

equals and we'll say this there is a helper built into this test case that we can use and we're gonna say go ahead and get the following route so what is the route that we are looking for well

this route right here is just slash customers so we're gonna go to slash customers and then I want to assert a redirect and where are we redirecting to login it's that simple

we're gonna fetch this URL and then I want to assert that we actually end up in logged in all right let's try that and see how it goes let's copy this run back here and then run vendor bin PHP

unit – – filter and then paste our method name and there we go we have one test and it's passing great so now let's change this code so we can see that fail so let's go back here and see how we can

do that well simple enough if we just disabled this middleware then it is no longer required for that so I'm gonna disable that let's go back to the test and run it again and we fail okay

and why did we fail we failed because the response status code of 200 which is obviously just a correct response is not a redirect status code great so that's it that is our first test how exciting

is that so let's go back here let's turn on authentication again and then run our test one more time and sure enough we are back to green now

I like to run clear right before every test like so that way every time I run my suite we get a nice clean screen so from here now that's just what I'm running right before all right let's

keep going with this test let's test some more stuff so now that we are testing that if you are not logged in you cannot see that page let's do the inverse let's make sure that if we are

logged in then we can view that list great so let's write a new test and say authenticated users can see the customers list so how do we do that well

there is a nice helper in level and we can say this acting s so we're going to be acting as a particular you this brings in an entirely new concept of testing so basically what you would

want to do is you would normally not want to use the same database that you use for your actual development for testing so what you can actually do is set up an in-memory database instead but

this database is gonna be totally fresh every single test so every time we run a test after the test is done it will erase the database and then when the next test starts you have to start over

so it actually has this refresh database straight here that we are currently not using and the reason for this trait is that same thing that I just explained is that we need to refresh the database

after every single test think of it as if you're gonna perform surgery so of course you want a clean slate and you want every single tool cleaned out and left completely sanitized so that's how

it is we want a clean state of our application before every single test so after every test we need to tear everything down and start over in the next test so let's set that up now let's

go back to this PHP unit that XML file we can actually use an in-memory SQLite obviously we've been using SQLite for our project but we can set up an in-memory one instead that way is not

using our actual file so in this PHP unit file we did disable telescope on here but if you noticed what these are these are environment variables anything in your dot EMV file can be overwritten

just for your tests so in my case this is what I'm interested in this DB connection so I'm gonna override DB connection just to make sure that it is always SQLite then on top of that I want

to make sure that the database that we are using is in memory so we can say DB database and then to put an in memory you put colon memory : and now that's it so every single test

is going to use these variables right here so whenever you need to set a particular configuration for testing only this is where you add them to the PHP unit that

XML file alright let's head back here to the test and who are we gonna be acting as well technically we don't have any users our database is empty but if you remember we talked about model factories

already we are able to generate any user that we want using a factory and that's the real reason for factories is it actually for testing so let's go ahead and whip up a user right now let's say

factory give me a new user and go ahead and persist it to the database now I'm gonna run into an error but I'm gonna continue to write the test so now we are technically logged in right because we

are acting as one of the users in our application so let's go ahead and copy this and this time when we visit slash customers because we are logged in I don't expect a redirect anymore

but rather I expect the status to be 200 so we can just say assert ok make sure that everything went ok all right we're gonna run into an error right here but let me go ahead and run this test so go

ahead and run vendor bin PHP unit and then your method name and there it is so this is the error that I was talking about now we have no such table users if you recall we talked about this already

we need to use this refresh database straight so whenever you get this error remember there is a completely fresh database in our system when the test is running so we need to migrate our

database so to migrate our database all we need to do is just use this refreshed rate so let's say use refresh data based rate just like that just that command right after your curly braket and let's

run that test again and now we get a green so now we know that only logged in users can see that page and we also know that if you are authenticated then you do get to see that page so what else can

we test let's go back to this controller and let's see what else we can put under test what about this this store method so if all of the correct data is sent to our store method we

you expect a new customer in our database so let's see how we can achieve that let's go to the web routes PHP file and let's check out this customers right here so as we see here when we hit slash

customers as a post request we do hit that store method great so let's go back to our test and let's add a new test for a customer can be added through the form okay so do we need to be acting as

somebody yes we do we need to make sure that we are logged in so we'll add that in at the top and then we'll say response equals this remember this time it's not a get request but it's actually

a post request so we're gonna be hitting the slash customers and then we need to pass in an array of data so as a second parameter we can pass in an array of data so what data is required well if we

go to the validate request we see that we need a name email active and a company ID so let's fill those out really quick let's say a name test user an email we'll just say test at test com

active we'll set that equal to one and then we need a company ID we'll also set that equal to one we actually don't have a company in our database at this point but we'll just do that as a one all

right so then we could say this assert count so let's grab it a customer and grab all of our customers in our database and I want to assert that there is a count of one when we fetch all of

the customers in our database now customer as a class is getting imported up here at the top as use app customer so just to make sure if you get a customer was not found

make sure you import that class so let's go ahead and try to run this test and see what happens all right so we get an error we fail asserting that the actual size of zero matches the expected size

of one so obviously something went wrong with our application and we were unable to do that so this cryptic message here is not going to help us at all and the reason

for that is that level behind the scenes is actually doing something called exception handling so it's actually kind of covering up the error for us so let's go back to phpstorm

and let's turn that off so we can say this without exception handling it's just a method call and if we add this line right here now lera will no longer handle those for us and I think our

error is gonna change let's try it again there we go alright so now we've got a much different error and the error that we were getting is this action is unauthorized of course we did not

authorize this user to perform that action all right now if you remember in our customer policy in order for you to create your email has to be admin at admin comm those are the only people

allowed to do that right and so in our controller right here right in the store method the very first line is we authorize that request but this is failing because our user right now that

we are generating whoever this acting as user is is getting a random email so we can actually override any of the fields inside the factory using the create method inside the create method we can

pass an array and override any of the fields and then let's go ahead and give them admin at so what that's going to do is the factory is gonna generate everything except this email

then it's gonna use whatever we passed into the email instead alright let's try that again and see if we get any errors so that worked however we get these messages here and if you remember that's

because we're actually firing an event when everything ran it actually ran through our event and it did fire this event but in testing we may not want to fire an event so how could we tell there

well go ahead and forget about any events so in testing there's something called fakes which means that you could basically replace one class with a different class so

it's basically a dummy class instead so we can actually get rid of this without exception handling because we know in our test is actually running so what we can do is we can call the event facade

and that's going to be illuminate support facades and then event and then we can call fake this is a built-in feature of laravel that allows us to fake any events and if you're looking up

here at the top that is imported so make sure that you are inputting the correct facade all right so let's run that test again notice how it took ten seconds that's a long time for a single test

let's run this again and see what happens there we go so much much faster 400 milliseconds and we did not fire that event so far we've ran one test at a time but if you want to run the entire

file then of course you could just say customers test just copy the class name instead so let's run that and instead of passing in a particular method let's go ahead and pass in a class name and there

we go so now we have three tests which we know that and we were served four things so figure that you are refactoring your code and all of a sudden you say you

know what I don't need this authorization here anymore and you delete that out of your controller entirely when you run your tests boom there it is that is sort of the premise

of testing if you're able to delete some code and it doesn't break your tests it means that you're not covering that so that's important that's a good test for you to know that's when you realize well

I really do need that line of code because obviously under test that is necessary all right let's just do one more test as this lesson is getting kind of long so let's do one more how about

this how could we maybe validate that this validation is happening so that would be a cool test I want to make sure that each of these fields is validated so if tomorrow I say well do I really

need this and I erase it I realize that yes I do in fact need that so let's do a quick validation test so let's say something here what I'll actually do is take this array

I'm going to need to use it over and over think of this array right now as sort of valid data 100% valid so let's go ahead and just put that out

in its own method and I'm just gonna call it data let's make a private function and call it data and it's just gonna return that array right nothing crazy so I make a change let's go back

to the test let's run them again yep we're still green all right let's keep going so that refactor was successful so now let's write a new test and let's just

say test a name is required we're gonna need all of this again so let me grab everything and let's go and start to modify this so obviously we're gonna be acting still acting as this person but

now instead of just this data what I could do is I could say array merge go ahead and array much what you get from this data but I want to override the name and just have an empty string

instead so let's see how we can test this we can actually use this response right here to make sure that we have an error so let's say response assert it has an error for name how about that so

the name needs to have an error let's go ahead and run this and sure enough it does pass but of course we want to make sure that we actually have zero customers and the reason we want to

certain that we have zero customers is because if a name was not provided then of course I don't expect the database to have a customer so that makes total sense let's go ahead and run that test

and we're back to green all right so that one is passing how about this let's make sure if you remember the next rule was that it needs to be at least three characters let's put that under test so

a name is at least three characters so now I'm gonna override the name and just pass in a just a single character so I still expect that to fail so let's do that now let's run that test so we run

our test and we still get green of course because that is still required all right so now let me see that fail what if I removed these two rules all together let me go back here run the

test yep we have two failures so we expected the name to be required and it wasn't that failed and then a name is at least three characters also failed so then

let's add required back in and go back here and now we have one test failed a name is at least three characters failed because of course it needs to be a minimum of three characters and now

we're back to green great now this up here right this is kind of becoming a little bit cumbersome as you can tell we're basically doing the same exact thing every single time

so why don't we extract that somewhere else how about this let's start with this event fake so at the top of my test I can actually override the setup method so if we override the protected function

setup and it does require this return type then we can maybe put that event fake up here instead so let's do event : : fake and now we can remove that from each individual test because what

happens is that the set up method gets run before every single test so any data or anything that you need to do before you run a test that's where it would go there is a set up method and there's

also a teardown method so if you need anything after you finish your test that's where it would go so now that we have that up here that cleans up our tests a little bit so now let me go

ahead and extract this as well let's extract this to a new method of this just call it acting as admin so acting as admin I'm gonna do the full refactor go ahead and replace go ahead and

replace there we go so phpstorm makes that a breeze so now instead of having all that complicated code all I'm saying is this acting as an admin acting as an admin and acting as

an admin and now we have a new method up here for acting as an admin let's clean this up let's get rid of that and let's actually make it private so that way nobody can really access this this is

not really a concern I'm actually going to move it down here now let's run the test again we did a big refactor but we have our test to back everything up and say you know what everything is still

running good great let's keep going let's do that email so now an email we could say an email is required all right so acting as an admin I'm gonna go ahead and send a blank email

field and now expect to see an error for email and I expect my database did not have any records all right let me run the test and we pass great now I also expect that to be a valid email I

repeated the test let's go ahead and change it to a valid email is required so now let's just put here some text test test test and let's run the test and yep we still

pass so if we were to take off for example the email validation and we run our test and of course it fails because that was not required and same thing if we take off the requirement then we have

two failures great so we're doing great let's get back to green so there we go so that guys that is a very basic example of testing and how to get it controllers under test so there is a lot

more to testing and we're gonna continue to explore testing in a future course but for now just know the basics of PHP unit and what testing does and once you get into the rhythm of testing I promise

you you'll never want to write a line of code ever again without writing a test first so with that that's it for today's lesson I'll catch you on the next one

Testing is an integral part of modern PHP development. Let’s work to get our customer controller under test.

For the best experience, follow along in our interactive school at

DigitalOcean Referral

Course Source Code

Hit us up on Twitter with any questions or comments @codertape (

About This Course

Ready to get started on your path to Laravel Artisan? In this series, we are breaking down all of the basics of Laravel to get you comfortable using the world’s most popular PHP framework. Let’s get started!

    add to database laravel admin laravel artisan Artisan Command artisan commend create artisan console artisan laravel command authentication in laravel belongsto belongsto laravel example coding tutorials create artisan command create command artisan create new command artisan Create your own crud laravel crud laravel tutorial crud resource laravel crud with file upload custom artisan commands custom artisan commands laravel custom laravel database configuration laravel database relationship digital ocean dotenv eager loading eager loading in laravel eager loading vs lazy loading laravel eloquent eloquent accessors eloquent mutators eloquent orm feature testing laravel fetching data in laravel filesystem laravel form requests full stack vue gate and policy laravel google optimize hasmany hasmany laravel hasmany laravel example hasmany relationship in laravel hasone hasone relationship in laravel How to create how to queue email how to use queue installing laravel intervention image laravel laravel 5 laravel 5 auth laravel 5.4 queue laravel 5.5 queue laravel 5.8 laravel 5.8 artisan command laravel 5.8 artisan console laravel 5.8 auth tutorial laravel 5.8 authentication laravel 5.8 commands laravel 5.8 crud laravel 5.8 crud example laravel 5.8 custom middleware laravel 5.8 deprecations laravel 5.8 eager loading laravel 5.8 elixir laravel 5.8 event listeners laravel 5.8 events laravel 5.8 feature laravel 5.8 features laravel 5.8 global middleware laravel 5.8 install laravel 5.8 lazy loading laravel 5.8 listeners laravel 5.8 middleware laravel 5.8 multi auth laravel 5.8 named resource routes laravel 5.8 named routes laravel 5.8 new feature laravel 5.8 own artisan command laravel 5.8 queue laravel 5.8 queues laravel 5.8 routing laravel 5.8 telescope laravel 5.8 tutorial laravel 5.8 user auth laravel 5.8 user authentication laravel 5.8 what's new laravel 5.9 laravel admin permissions laravel api laravel artisan command laravel artisan console laravel assets laravel auth laravel auth role laravel authentication laravel authorization laravel axios post example laravel background process laravel background task laravel background worker laravel beginner to master laravel belongsto laravel belongsto vs hasone laravel belongstomany laravel best packages laravel best practices laravel best tutorial laravel bootstrap laravel command laravel command line laravel commands tutorial laravel composer install laravel connect db laravel console command laravel console testing laravel contact form send email laravel controllers laravel create laravel crud laravel crud policy laravel custom command laravel custom middleware laravel database laravel database configuration laravel database relationships laravel database seeder laravel database settings laravel db seed laravel db settings laravel debugging laravel deploy aws laravel deploy digital ocean laravel deploy on digitalocean laravel deploy on server laravel deploy to production laravel deployment laravel digitalocean laravel drag and drop file upload laravel dropzone file upload laravel dropzone image upload laravel e-commerce laravel eager loading laravel eager loading tutorial laravel eager loading with condition laravel elixir laravel eloquent laravel eloquent belongsto laravel eloquent crud laravel eloquent hasmany laravel eloquent where laravel eloquest tutorial laravel events and queue laravel events tutorial laravel factory laravel feature test laravel feature testing laravel fetching data from database laravel file storage laravel fillable example laravel flash message notification laravel for beginners laravel form validation laravel forms laravel forms bootstrap snippets laravel from scratch laravel from the ground up laravel front end laravel frontend tutorial laravel gate and policy tutorial laravel global middleware laravel hasmany laravel hasone laravel i18n laravel installation laravel installation mac laravel intervention image tutorial laravel javascript tutorial laravel jobs queue laravel language laravel language switcher laravel language tutorial laravel languages laravel lazy loading laravel listener event laravel listeners laravel localization laravel login laravel mailable tutorial laravel many to many relationship example laravel markdown email laravel markdown mail laravel mass assignment laravel middleware laravel migrate fresh laravel mix laravel mix 5.8 laravel mix vue laravel model factory tutorial laravel multiple language laravel mysql json laravel named resource routes laravel named routes laravel new features laravel news laravel nginx laravel node modules laravel npm install laravel npm run watch error laravel one to one laravel paginate laravel paginate link laravel pagination laravel pagination 5.8 laravel pagination links laravel pagination tutorial laravel phpunit laravel phpunit testing laravel pivot table example laravel policy laravel preview laravel query optimization laravel queue laravel queue event listener laravel react laravel register user laravel registration laravel registration validation laravel relationships laravel reset password laravel role middleware laravel role permission laravel roles laravel route group middleware laravel routes laravel routing laravel routing with parameters laravel sass laravel save file to database laravel scopes laravel seo tutorial laravel server laravel session data laravel ssl laravel supervisor laravel tailwind css laravel tailwind setup laravel tdd laravel tdd tutorial laravel telescope laravel telescope installation laravel telescope tutorial laravel test driven development laravel test workflow laravel testing laravel testing controllers laravel testing tutorial laravel tools laravel translation laravel tutorial laravel tutorial for beginners laravel ubuntu server laravel ubuntu tutorial laravel unit testing controllers laravel upload file laravel upload image to storage laravel upload multiple files at once laravel upload multiple image to database laravel upload multiple images laravel url slug laravel vue laravel vue js crud laravel vue setup laravel vue tutorial laravel vue.js laravel webpack error laravel webpack tutorial lazy loading lazy loading laravel lazy loading vs eager loading learn laravel learn laravel framework step by step localization in laravel localization laravel many to many laravel mvc tutorial for beginners in php mvc tutorial laravel mysql mysql relational database mysql relationship n + 1 problem n + 1 problem laravel new artisan command npm install npm run dev npm run watch laravel one to many one to one one to one laravel own artisan command pagination pagination bootstrap pagination bootstrap php mysql pagination laravel pagination laravel bootstrap pagination links pagination php php php artisan php carbon immutable php framework php framework 2019 php what's new 2019 phpunit phpunit laravel phpunit testing pivot laravel policy laravel polymorphic relationships queue and events queue email queue for laravel queue jobs queue laravel 5.8 registration form relationship laravel eloquent relationship mysql restful controller role permission in laravel roles laravel route model binding laravel routing in laravel save image to database seo friendly seo friendly content writing seo friendly website seo laravel seo optimization simple pagination laravel slugify sqlite relational database sqlite relationships between tables symfony command console symfony laravel tailwind css tailwind laravel mix telescope laravel tutorial laravel unit testing unit testing laravel upload file laravel upload file php upload image laravel upload image to database php upload images laravel upload multiple files in php url slug laravel url slug php vue vue.js vuejs laravel why use laravel queue why use queue