Test Driven Laravel
Lesson 5 of 6 · Test Driven Laravel – e05 – Book Checkout & Book Checkin Flow Feature Test With TDD – Part 2 · Education
Test Driven Laravel – e05 – Book Checkout & Book Checkin Flow Feature Test With TDD – Part 2
welcome back so we have a little bit of cleanup that we have to do before we can jump into this lesson after the previous episode we wrote the book reservations
test but we didn't really create any commits for it so let's go ahead and wrap that up before we start this lesson so all we need to do is really just put all of this into a single commit it is
all related to each other so we can simply just say book checkout sequence so we'll go ahead and do that and we can get started with this lesson so as we spoke before in the previous
lesson we have the unit test in place for the book reservation but what would happen now is if we had an actual view where you could do this there would be no way for us to actually perform these
actions and that's where a featured test comes in we spoke a little bit about this in the previous lesson and we actually took a look at the book management test where we see that we
actually hit a particular end point and then we're just asserting that the correct action was taken by application by seeing how the database changed before and after we hit that end point
so we need to do the same thing for the book checkout and check in sequence so let's do that now let's start with the test PHP artisan make test and for the name how about book check-out test of
course we did not pass the – – unit which made that a feature test and if we go back here we do see it inside the features directory alright so let's go ahead and open this up but I will keep
this test behind here because I basically need an endpoint for each of the actions that this test can perform all right so let's go ahead and clean this up and start a fresh test and this
very first test let's see what it's going to be so the first test in the unit is a book can be checked out now for a book to be able to be checked out then of course we need a book and we
need a user so we can take a different approach with our feature test we can basically assume that we are going to be using the authenticated user to perform this action as opposed to having the
user be passed in so with that all we really need is just a book so let's write a test that's as a book can be checked out and maybe let's be specific and say by a signed in
user so obviously if you are the signed in user you are able to check out your own books now we did speak about a separate version of this where perhaps a librarian would be able to check out a
book for you instead of you being logged in the librarian would be logged in we are not ready to tackle that so we're going to go ahead and ignore that for now but we are set up to do that
so that's gonna be a nice little additional thing that we've already pre planned so what kind of end point are we gonna hit well I know for a fact that first of all I need to be acting as a
user so let's go ahead and create a factory and then use the user class to create a new user and then we're gonna go ahead and hit a post route and perhaps we could just call it slash
check out slash maybe the book ID so that reminds me we do need a book otherwise we wouldn't be able to check out a book so let's create a book as well so factory book class and we do
need to create that in our database so go ahead and hit the create method on it and do notice up here we are importing book and user so we're gonna hit slash checkout slash book ID we can grab that
simply from this variable that we created there so if this action performed correctly then of course what can we assert well basically we can assert the same exact thing that we are
asserting here so let's go ahead and move that over and we do need to import the reservation so let's go ahead and do that so now we have a preservation up here and so the user ID well we can't
really just say user because the user ID would be the one that's in here but one thing we can do if we wanted to is we can actually inline save user this is a PHP feature that you can use where you
can actually assign a variable right in line this of course would be the same thing then if we grab this and put it out here and then we passed in user same exact thing it does save you a line
so I will use that approach from time to time so we are going to go ahead and hit that endpoint acting as this user and then we're going to use that user to make sure that we in fact did create the
correct reservation all right let's go ahead and run this test PHP unit that's just filter I do have a little shortcut for that so we don't have a table for authors so we know that's because we are
not refreshing our database so let's go ahead and use refresh database trait and let's see now so now of course we get to the part where well it doesn't quite work but we don't get the correct error
now if you remember that's all these feature tests typically to get them started you have to run with that exception handling the exception handling is just the way that laravel
bubbles up exceptions into proper HTTP responses with the correct error so that feature actually we need to turn off for us to actually get the underlying error so our test and now of course we get a
very different error now when we get now is did not found HTTP exception we know that of course there is no route at checkout so let's go ahead and add that route now let's go to our web that PHP
file let's create a whole new section here for route post and it's gonna be slash check out slash and then we're gonna have a variable so if you remember of course we use the curly brackets book
and then let's hit a new controller to check out book controller at and what are we gonna hit what would make sense if we are creating a check out so let's go ahead and call that store now of
course we could rename this instead of check out and just call it reservation reservation is kind of a big term we're gonna end up adding a feature that allows us to actually reserve a book and
then go ahead to the library and check it out so that's why I've called that reservation but for this particular instance check out will be perfectly fine that way we have a nice controller
that all it's able to do is just handle the checkout flow for that so that's very nice let's go ahead and run our test and of course our controller is gonna fail now
it says checkout controller does not exist all right let's go ahead and create one PHP artisan make controller checkout book controller and run that test again so now we get that the store
method does not exist all right so let's go back here to the checkout books controller and let's create a new store method store and there we go run that test again and now we do get to the
final part here where we are asserting that we don't in fact have that now the nice thing with us writing that unit test is that well we kind of know what needs to happen
we know that essentially all we need to do is this code right here so let me copy that code and let's bring it over to our store method so how do we get book well of course we can use route
model binding we have this book here and that's going to be a variable of a book so we can accept a book so remember in level all we need to do is type int book as a model notice how it's being
imported up here and now we do have access to that so the user how do we get our user like I said we're gonna presume that we're talking about the authenticated user so I'll go ahead and
grab that now and then let's go ahead and run that test and sure enough we are green awesome of course we do need to test that only signed in users can actually check out a book so let's do
that now let's write a new test and let's call this one only signed in users can check out a book the setup is going to be almost identical I'm go ahead and grab all of this let's bring that down
here and actually just to clean up on the first test we don't need exception handling anymore we know that test is passing so what do we need to do here well instead of
acting as something let's go ahead and delete that line altogether and attempt to hit that post route now I do expect this to go ahead and just redirect us into the login page because of course we
are just assuming that they forgot to login and then what we could do is we can actually assert that the count is still and of course we're not going to have
any reservation that we can grab so we would not be able to test those three things now this additional step here is just a little bit of extra reassurance that we are in fact making sure that
nothing got created in our database by accident now we know that's not really possible but it's always good to test let's go ahead and run that now and of course we fail we get a 500 and I bet
you what this is is the fact that we are trying to grab the authenticated user but it doesn't exist so let's run that test without exception handling very different error this time and there it
is so we are trying to get property ID of an on object that is in book line 20 let's check that out let's go to the book model and in line 20 we see here that of course we're trying to create a
reservation and we're trying to grab the user ID but at this stage user is actually no so of course as we've done before to fix this we need a constructor and in the constructor let's go ahead
and use the middleware for auth laravel just makes this such a breeze to do and with that we are getting an invalid argument exception and I'll show you why that is in just a second but before we
get to that let me turn on exception handling and run that test again so now we're getting a 500 and this can be a little bit confusing and you may not understand it right away but here's one
thing yes we are using levels authentication but we have actually never ran the make off command in this project so what is happening is if we run PHP artisan route list you see that
in none of our routes here we actually have the route for login or logout or anything like that again this just has to do with the fact that we have in ran the PHP artisan make off so it is time
for us to run that so let's go ahead and make off right now so PHP artisan make off and now check out the difference this of course is my list of routes before I ran make off let me run that
command again that's route list and now very different result we do see a lot of new routes here including login including logout of course your password reset sequence
registration methods a lot more stuff is in this right now in contrast of course down here it would only be the routes that we've defined ourselves let's go ahead and run that
test one more time and let's see if we can get green and we do let's run that whole test not just the two tests that's run the whole file PHP unit dash dash filter and we are still good to go great
let's move on to the next section how about this what if I give you a book that doesn't exist that's a whole nother section only real books can be checked out we're gonna borrow a lot of the same
logic from up here but I'm not actually going to create a book so instead of using the book ID we need to hard-code something in here I will just put one two three so of course at that point we
would want to assert that we have a count of zero and there is no reservation but what else should we really expect here I mean if a user does that then really we're expecting a 404
so let's go ahead and make sure that we assert a status of 404 meaning nothing was found for ID one two three all right let's go ahead and run that test and sure enough we are still green great
we're making great progress let's go back to the unit test and let's see a book can be returned all right so let's go ahead and tackle that now so up here we have a book can be checked out by
assigned end user let me grab that and we're gonna modify it let's make a new test a book can be checked in by a signed in user and I'm gonna copy a lot of the same stuff up
here but of course we're gonna want to do the setup slightly different so we are still going to need to be signed in as a user and then let's go ahead and do a check in for that book ID but that
reservation is not going to exist at this point so we have two choices can either go ahead and hit this checkout endpoint first and then go ahead and try to do the check-in point
or we can actually go into our database and create a reservation so I'm gonna tell you a little bit about both approaches and what I mean by that is the following we can really simply grab
this and put it over here and that would become part of the set up for this test of course it would probably be better if we grabbed the user and just created a single user and then instead of actually
reassigning this we would put user so that's what it would look like I think this approach is the better approach because the other thing that you can do is instead of having something like that
for your setup you could actually go into your reservation and go ahead and create a record in there where basically your check-in time is maybe now sub days let's just say two and then the user ID
will of course be some sort of user ID that you grabbed from up here so your user ID would be the user ID a little bit of pseudocode here but you get the point so the reason why I like the
approach of actually hitting the endpoint is twofold first of all we are doing a feature test so of course we're basically testing the entire sequence through and through but the most
important reason I feel is that when you're hitting the end point any changes that you make to that endpoint would break your test if all of a sudden we had this new field we'll call it a
random field in our actual implementation we would actually have to go back to each test and make sure that we added that back in because we are manually creating the reservation for a
feature test this feels too low-level it's good in our unit test we're basically doing the same thing we're grabbing factories were creating we're grabbing the model directly we're
manipulating it we're making sure that everything checks out that's low level that's unit testing we're feature testing this absolutely to low level of a test for a
feature test so I'm gonna take the previous approach and actually hit the end point that way we know we're testing the full sequence so let's create a book let's create a user then acting as that
user I'm going to check out that book and then I'm going to attempt to check it back in so at that point we still believe we should have a single reservation and the user ID should still
match with the book ID however they're checked in time should also be now we can actually check both if we wanted to this is just a little bit of redundant testing because we do have this initial
test up here that already tells us that the checked out sequence is working properly but it never hurts to add an additional assertion here so let's go ahead and run this test now and so we
get did not expect the type of object that is in line 66 so line 66 is this line right here and of course it's basically just saying well there's something happened here because I don't
have it checked in time let's go ahead and disable exception handling again as we've done before and let's run that test again so now we get the correct error not found exception we know that
there is no check-in route at all so let's go ahead and grab that check-in route and go back to our web routes and add a new section here for check-in now notice here the author outs that is what
actually got added by make off we didn't really check that out we're not too concerned with that remember we're concerned just with the backend for now so this of course is gonna hit a
check-in controller so a check-in book controller which is not gonna exist but let's go ahead and run the test let the test guide us there and there it is so there is no check-in controller till PHP
artisan make controller check in book controller let's run the test again now should be something about the store method and sure enough there is no store method back to phpstorm
check-in controller create a new method for store run the test again and now we're back to the same error but this time we know what is going on all right so let's go back to
our unit test and a book can be returned all we need to do is grab our book and check in a user all right perfect let's go back here of course let's go ahead and accept the book and accept that as a
variable remember we are importing up here at the top and then all we need to do is check in that book now in terms of the user again we're gonna grab the authenticated user and make sure that
that is properly working all right let's run that test again we are good to go so we are passing all right let's keep going through this let's go back here and let's create sort of the same
basic tests that we have here let's go ahead and copy these other two tests because we need to create something very similar for the check-in sequence so I'm gonna paste these in and let's modify
them for now I will just comment out this one and let's say only sign-in users can check in a book all right so we're gonna create a book we're gonna hit the checkout
that's just set up for now but then we're gonna hit the check in with a book ID and I do expect to be redirected as well all right let's run that test now we get a 500 which is not a redirect
status code and if we want to see what the real error is of course without exception handling will fix that for us and there we go so the error that we are getting is on
authenticated now for this this is a little tricky right so we are gonna need to be authenticated for this part that way we can get the sequence correct so let's add that back in let's say this
acting ass and then let's go ahead and whip up in a factory let's grab a user and then we'll go ahead and persist that does that make sense remember this is all part of the setup we are really
testing check in not check out putting order for us to create a proper check out we do need to be signed in all right let's go ahead and run that test all right and so we get a 200 which is not a
redirection error and that is in line 79 in line 79 is right here now of course because we are signing in we should not be redirected that's a mistake on my part let's get rid of that line and now
we've moved on to line 81 so of course in line 81 that's what we are supposed to be redirecting but we are not so let me go ahead and get rid of exception handling and let me now add that in to
my check in controller so in the checking controller as we've done before let's add a constructor we'll just say this middleware off run that test again now
when we run our test we run into a bit of an issue here so we're actually back to line 79 but if you remember line 79 is this line over here doesn't quite make sense why are we not redirecting
once it turns out when you log in a user level actually continues to have that user be logged in so we actually need to log out but how do we log out well that's fairly simple we're gonna use
auth we're gonna use illuminate support facades off and then we can call the logout method on it now this did get imported at the top let me show you so if we go back up here there it is
illuminate support facades off so now we're gonna log in but then we're gonna log out to hit the check in that way the check out sequence works perfectly and we're still able to check in a book
without being authenticated let's do that now and now we've moved on to the next error in line 84 let's check that out so of course we are failing to assert a
count of zero which makes sense because what we're actually asserting is that we have a count of one but if we grabbed it then I expect this to be known that in at time should be not so let's just
assert no and then we only need one parameter for that so this is gonna assert that the checked-in at is still no meaning the book did not get checked back in run our test we are green great
let's move on to the second test so only real books can be checked in do we really need to test this well it's gonna be a little tricky for us to even check this because it shouldn't have been
checked out to begin with we should still be getting a 404 so on better terms I don't think we really need to check this so I'm gonna go ahead and delete that test let's run the entire
test and now we're getting that and let's run the full suite and we are still green we're making great progress on this let's check out that unit test see if there's anything else let's check
this out so if not checked out an exception is thrown so if we try to check in a book that is not checked out then we're throwing this exception now in terms of the UI what do you expect to
happen well I really kind of think that it should just be a 404 if you tried to check in a book that's not checked out really it should just be a not found I think that's good enough for our tests
so let's do that now all right new test a 404 is thrown if a book is not checked out first let's look at the happy path up here so our happy path for checking in is right here a book can be checked
in and actually on that note we don't need exception handling let's go ahead and clean that up all right let me grab this whole entire test we're gonna have to modify it of course but we'll paste
that in whoops looks like I misspelled first there we go all right so we're gonna create a book we're gonna create a user but then we're not gonna check out the book right so we're gonna go ahead
and get rid of that and then acting as that user we're gonna go ahead and check in that book even though again we never checked that book out so we really assert that we should not have a
reservation rights a reservation zero but furthermore we should probably assert that 404 so let's say assert is a status of 404 let's see where that gets
us PHP unit that's just filter all right so it says code 404 but received a 500 all right you know what to do now so without exception handling let's go ahead and
run that test again and now we get an exception so we are getting our exception in book line 33 so how would we handle that well if we're going to our check-in controller I'm gonna
attempt to do this but let's go ahead and wrap it in a try-catch block so let's try to do that and then let's catch basically just our normal exception as e if we hit the catch block
let's go ahead and return a response and we're not gonna pass in any data but as a second argument we do need that 404 so we'll run that and let's run the test now and now we get green awesome all
right let's go ahead and get rid of exception handling and we can run this entire test just to make sure we didn't break anything yep still green all right let's go back to the unit test let's see
if there's anything else for us to do so a user can check out a book twice well this is a good one for us to test but really we've already tested this in the unit section so we don't really need to
worry about the endpoints we know that the endpoints are just basically going to do the same thing of checking in and checking out a book so because we have the unit test we're not concerned with
that so that's it with that we do have the check out and check in in sequence for our application so this is what we're gonna end up with a book can be checked out by a signed in user so this
is what I would call the perfect path this is the path where everything went right there is a book we checked it out everything worked out perfectly then we start to go into the edge cases where
something didn't quite go right in this case what we're testing is that the user is not signed in picture something like the user accidentally logged out then hit the back button and
something happened so we still want to make sure that we take them back to the correct page then this path is the really bad one this is the one where this is not even a real book this would
be where the user basically goes in and hijacks the URL in their browser and tries to do something malicious by trying to check out a book that doesn't even exist so we still want to make sure
that we are properly responding to that user then we do the same thing for our check-in so this is our happy path check-in sequence where we do have a book we do have a user we go ahead and
acting as a user we do checkout the book and then we try to check it in then of course based on that we know that everything worked out now that not-so-happy path of course would be if
the user was to actually not be signed in for the check in sequence but of course we do need to check out the book properly and that's why this whole section here of the setup of this test
is acting as a user and then finally we do have a 404 if a user trusted for some reason somehow check in a book a real book that they have not checked out so this would be the sequence where
obviously something went wrong when that book was supposed to be checked out but it in fact was not checked out and so when we try to check it in of course everything blows up because there is no
reservation so there's not much we could do for the user at that point the best way to do is show them a 404 saying well we could not check that one in that one is not gonna work so with that let's go
ahead and look at github and see what we have made so we do have a lot of files here but they basically have to do with the fact that we ran that make off command so a lot of the stuff in here is
stuff that we didn't really work on but rather is the templates that get posted to your project whenever you run the make off command so all we really need to do here is say a commit of something
like make off and check out feature test let's commit that change and let's go ahead and push all of our changes into github so there we are now do remember that all of the code for
this series is available up and github if you go to github.com slash coders tape slash library we do have the code here if you are following along so last not least of course don't forget to
subscribe if you have any comments or questions go ahead and post them below and I'll try to get to those in a timely manner but that's it for today go ahead and implement this in your project and
when you're ready let's move on to the next lesson
From unit tests into features test. Let’s handle the actual endpoints that our app will consume to checkout and checkin some books.
For the best experience, follow along in our interactive school at https://www.coderstape.com
DigitalOcean Referral
https://m.do.co/c/7dce5364ef4d
Resources
Course Source Code
https://github.com/coderstape/library
Hit us up on Twitter with any questions or comments @codertape (https://twitter.com/CodersTape)
About This Course
We are tackling a brand new series to showcase a real-world workflow that you may adapt to your liking. We will be building a library management software for our local library that will include the ability to checkout books, reserve books and much more. We will be doing this through the TDD methodology, come along as we dissect Test Driven Laravel Development.