Test Driven Laravel
Lesson 2 of 6 · Test Driven Laravel – e02 – Deleting a Record, Asserting Instance Of & Carbon Parse · Education
Test Driven Laravel – e02 – Deleting a Record, Asserting Instance Of & Carbon Parse
and we're back for episode 2 of test-driven laravel before we get started I did want to show you how we can upload our project to get hub you
may already be familiar with this but a lot of this project again has to do with how I treat my workflow every day so let's head over to Chrome and I am logged into the github page for coders
tape I'm gonna hit new for new repo and we're simply gonna name this library and description for now we'll leave blank we'll make this a public repo and we'll just go ahead and create a repo now
remember we did already initialize our repository right here right if we run get status obviously we do have a repo already so with that we need to follow this step right here push an existing
repository from the command line so what this first line is gonna do is gonna set our remote server because right now if we do get push it says well where am i pushing I don't know where to push your
code so that's what this line is gonna do is gonna set the remote to that github repo that we just created now that alone doesn't do anything because of course we need to actually push so
what this push will do is match our master branch up to github so let me go ahead and push that change and now if we go back to crumb hit refresh there we go our project is now up on github now this
project is available up on github so as you can see here we have all of the commits that we created from the first video and of course you can go back into this as it is in public repo and you can
check out each step that we've taken throughout this project so keep that in mind throughout the rest of the episodes all right so let's head back here and the very first thing I want to do is I
just want to run all of my tests and everything is passing let's continue working on that book reservation test that we've been working on since lesson one so let's open up the book
reservation test and let's see so we have a book can be added to the library and actually from the previous episode we did leave this without exception handling
I believe we can get rid of that and we can run all of our tests and we should still be green we're not really bubbling up any exceptions anymore so we don't need laravel to not handle those for us
all right what else do we have so we have of course what we're doing in terms of validation and lastly we do have this update so we are able to hit a patch endpoint with the ID of the book and
we're able to update our book so what's next let's go ahead and say a book can be deleted that would be sort of the next step so also actually with this test here I can get rid of that line as
well let's check that out yep we're still green now the error that we got here is just the fact that we have a blank test so don't worry about that one so let me go ahead and
basically do a lot of the same stuff that I have here so let's do the post request so we can add a book to our database and then let's go ahead and fetch that book and then let's submit a
delete request to slash books slash book ID and with that we should be able to delete that request so what can we do well how about this assert count of zero if I fetch all of my books that makes
sense now one thing we can do is we can add an interim check right before we hit the delete so that would look something like this we can actually copy this line and what I can do is I can assert a
count of one that way we know that this route did in fact add a book into our database however really this is already proven up here in this initial test so it's not really necessary this is really
up to you do you want to do that or not that's up to you how you write your tests so this would be a really explicit way of us verifying the fact that this was added because otherwise the assert
count could have been zero all along say if we didn't actually do that as a matter of fact if we don't have anything like this I think this test would actually even pass let's check it out
yeah you see this test passes and we're really not doing anything as you saw here before I actually erased everything so what this will do is once I add this line now this test will fail
so that's what this line is doing that line is making sure that we are in fact passing right before we actually perform the action obviously two different schools of thought on this on whether
you should do that or not a lot of folks will tell you well if your first test passes right which would be kind of our book can be added to the library so then there's no reason to question whether
this code here would actually add a record to our database but again entirely up to you if you want to do that all right so let's go ahead and run this test and let's see what kind of
errors we get whoops there we go all right so obviously we're having the same issue we've been having a long which is that we are not getting the fact that this
route right here doesn't even exist so as we've done before let's do without exception handling and run the same test again and sure enough now we get that the delete method is not supported by
that route so let's support that route by duplicating this and let's make that a delete request and we're gonna hit the destroy method all right let's check out our error now and now we get books
destroyed does not exist of course in our books controller so let's go to books controller and let's add a destroy method actually that would be up here so I like to keep my public methods up at
the top and then any helper methods will actually go underneath so add a public method for destroy so obviously at this point I would have to go ahead and say well I'm gonna need my book and that's
gonna be passed in through the route all I need to do here is say book go ahead and delete all right let's run the test and sure enough we are passing now here's one thing we have not really
tackled so I want to go back and backfill a little bit so obviously up until this point if we take a look at all of these tests for example let's take deleted tests for example yes we
are asserting that the correct action got taken place in our database but what about redirections where should you be going after you are deleting a record well I think you
should go back to the index method right and we don't really have that one just yet but we could say something like this so once we hit the delete then I do want to make sure of course that we don't
have any books in our database left but I also want to make sure that we are redirected and where do we want to be redirected to probably to slash books meaning just the index method of slash
books as a get request so now our test is going to fail again because we're obviously not redirecting so let's go ahead and add that to our controller here so we're gonna delete and then
let's go ahead and return a redirect to and you can do this a books right here or of course we could use a named route which eventually will probably switch over to so now we're passing for that
one let's check out what other methods we can do that in so a book can be updated so if you update a single record I figured it makes sense for you to get redirected back to the details page or
the show view of that method so let's say here response assert redirect and where should we be going to we should be going to slash books and then slash the ID of the book so let's just go ahead
and say book and this is gonna be idea of one so yes theoretically you could just say slash one and that wouldn't be a problem it's up to you sort of how you want to do that so a book can be updated
let's run just that test and we fail alright let's go back here and let's go to the update so after you're done updating that record let's go ahead and return a redirect back to slash books
slash and then we need the book ID fair enough let's run the test again and we are passing alright let's finish up here by going to the first method here we can
add it to a library so after you're done doing that I definitely do want to make sure that we are redirected to last books / again the ID of the book so we don't have a book here just yet
because we created that route so we'll actually have to grab the book somehow so say book give me your first one and then now we could just say book ID oops there we go alright so let's run let's
run this one test again and we are failing because of course we're not doing the redirection and so let's go back to our controller and we're gonna need basically this exact same thing
that we have here now where are we gonna get book from well as it turns out the create method will actually return us back to the instance so why don't we save that to book and then we can use
book right here to actually redirect does that make a pass nope it does not so we got a 302 and it not match the 200 all right let's see what happened here so we got a redirect which is a 302 ah
right here so initially we were asserting that we were getting a response of okay I really don't need to do that if I am asserting that I'm getting a redirect so I think that'll
make it pass and let's run the whole suite yep and everything is passing all right so now in a green state let's do some quick refactoring here so one cool thing that I like to do is so a book
right so if we have an instance of book an instance of book should be able to redirect this to itself and what I mean by that is I just want a quick little helper in my book model what I am able
to just say go ahead and give me the path to you since you are a model you should be able to know where it is that you are located on so with that in mind let's go to the book model and in the
book model let's add a new public function and I'm just gonna call it path now this is very common for me I do this on every single model that way I don't have to worry about how to construct
each one of these every single time because truthfully we can actually grab this and right here in this little method we could say return instead of book of course this is this because we
are inside a book instance and so now what we could do is if we have a book then we can actually rewrite this as book path and that will give me the path to this book it's that
simple let me run my test yep we're still green so let me go ahead and perform that refactor throughout my entire controller and this is kind of the beauty of being able to have tests I
can go back recheck my test we're still passing let's keep refactoring how about this one here obviously this one is back to just a source so I can't really use the book itself but that's okay we were
able to change the edit and the store view with that little path now the nice thing with this is if tomorrow I decided that from now on my address will maybe look something like the ID and then
maybe I want a slugga fired version of the book title how about that so we'll have the ID then I want to have a dash and then why don't we add str as the helper that's aluminate support slug and
then what are we going to slug this title so that would look something like you know slash books slash one and then maybe – and then let's think of a book named Enders game for example that's
what my address would actually look like making that change right here in my path would that break my test let's find out yes it does and the reason why it does is because we are actually expecting an
actual string so we should probably maybe change that in our test this will make my tests a little bit less brittle so let's do the same change right here that we did and I'm actually just gonna
copy this anywhere that I find it and I'm just gonna say book give me your path all right let's see now now we are failing but we are failing not because my tests are failing but because I
actually do need to accept that in here so let's say we're gonna have a book but then there's gonna be a dash and then maybe let's just say I am gonna have the slug how about that
does that make my test pass almost so we have cool new title line 66 all right let's check that out let's go back here let's go to line 66 so we are redirecting to
book path however when we perform the patch this book right here is not being fetched again so when that happens we need to actually grab a fresh copy of this because we grabbed it before we
actually did the patch request so what we're gonna have here is still old data of course my old title is still gonna be here however if I run fresh that will actually go out to the database grab the
record again and I think that will make my test pass and it does so now my tests are a little bit less brittle because again instead of just using a hard-coded paths I am using the same path helper as
we are using so this is another one of those it's a choice you can of course not do this refactor and keep your actual paths that's entirely up to you how you want to do it now for me I am
gonna revert a little bit of this not all of it let's get rid of the slug section and then in my model I will actually just do this ID on my tests still passing yes they are
so how awesome is that we were able to go down a rabbit hole figure out if we were gonna like it or not having our tests to back us up and say are we still green and if we are green then we're
good to go if we're not green then you know something is not quite right but we go down these refactor paths sometimes they're successful sometimes they're not sometimes we want to go back
and say you know what maybe that was not the correct path let's start over but you still have your tests to back you up and saying yes this implementation and this other implementation both do the
exact same thing and meet the requirements of our application that's the beauty of tests so let's keep going let's keep rolling through these so what else can you do well first and foremost
I actually don't need that without exception handling anymore and sure enough we're still green so I was initially going to do book reservation test all together and that's why I named
that I will actually just refactor this and let's rename that test to book man test I think that'll be a better title for it let me run all the tests yep we're still
passing we're good to go let's make a new test how about PHP artisan make test make test and what's the test gonna be well it's that's gonna be the author management test how about that let's go
back here all right let's delete this and let's add a new test how do we want to handle this well what about this we're still gonna need to be able to do everything an author
needs to do so we probably still need a controller and a model and all of that so why don't we start there but however we still need to connect these two tests at some point so let's say an author can
be created so let's say this this post so if we do a post request to slash author with some data and maybe the data will be the name we'll just say author name and just to keep it simple maybe
we'll just say their date of birth a little bit biography stuff so for this we'll have a date here so this date obviously will need to be a carbon instance so we're gonna learn a little
bit about how to cast something into a carbon instance and that's why I'm choosing to go this route alright so if we hit that end point then of course I want to cert the count to be one in my
authors table if I fetch all the records obviously author does not exist just yet so we're gonna get a couple of errors here now as before let's go ahead and disable exception handling and blessin
not least let's go ahead and refresh our database right that way we do have a clean database all right let's see what we have so first and foremost of course that route doesn't exist let's take care
of that back here let's add a whole new section of routes here post route of slash author and we're going to hit an authors controller at store method great let's make that now so PHP artisan make
controller authors controller to run our test again and so obviously we're going to get that the store method does not exist in authors controller all right back to phpstorm
authors controller let's make a new public function store and let's run the test again and so we get that this author class that we're trying to fetch does not exist we know that's a model so
let's go ahead and say PHP artisan make model author and I do want a migration with that so now that we have that we need to go back here into our test and make sure that we actually import that
class so I'll import the app author class and that is up here at the top and let's run the test again all right so finally we do get to here and say you know what that does not exist all right
so let's go into phpstorm and let's pop open this controller and let's do some work so let's do it the way that we always do let's go ahead and grab our author the app author and let's
go ahead and try to create an in it here's another trick I can do it the other way that we did it but here's also another trick you can grab the request and then you can say only and – only you
can pass in an array so let's say only give me the name and the date of birth so that will ensure that we are not passing the entire request but rather just these two things now of course
we're gonna get a mass assignment error and it's gonna say something like fillable must be there we go add name to fillable so of course we go into our author model and let's go ahead and set
guarded to an empty array go back here do it again and now we get of course author has no column name name of course we create our migration but we didn't do anything with it let's go
ahead and create that now alright so we're gonna have a table string for their name and then we're gonna have a time stamp and it's gonna be date of birth we know that all right so let's go
back here run that again and sure enough we are getting green okay a couple of things that I do want to check and that is related to that date of birth of course nothing special about the name of
the author that's just a string nothing to see there but what I really want to know here is the following this assert instance of I want to make sure that that date of birth is coming back as a
carbon instance meaning that we are properly parsing that as a date so how do we do that well we use this assert instance of and let's say carbon carbon class now carbon is being imported up
here as carbon carbon so I want to make sure that if I fetch my author and you know what we could do this in two ways we could either save this right in line here that would work or of course we
could just take this out of here so let's do that that route I think it'll be a little cleaner so we'll open that and then here we'll say author so right here if we grab our author and then we
grab the first one right because this is actually going to be a collection so if we grab the first one and then we grab their DOB or date of birth then I expect that to be an instance of carbon so of
course this is gonna put us back in the red and there it is so it says that of course I felt asserting that this string that you gave me is actually an instance of carbon so check this out this may be
a totally new thing for you but if we go to the author model there's this protected date and this is actually equal to an array so if you have several columns in your database that you want
level to automatically cast them into car in instances the same way that updated at or created at works you could put them inside this protected dates so
let's add DOB to this let's check out our test now so now we're going to get something about the fact that it's not properly being formatted right it says unexpected data found unexpected data
found data missing so everything kind of blows up a little bit and a lot of it actually has to do with the fact that yes we are trying to fetch and put a date in it but we're not telling laravel
how that date is formatted we need to be able to format that so we need a way of hijacking when that creation happens and we need to tell that about what we're actually doing so check this out
if we run right here there's a whole set of mutators and setters that level has and their follow a very specific naming convention so if you follow the same convention then it will work for any
field that you have so check this out let's make a new public function for set DOB attribute by doing set and then your column name it is a little bit weird in this one because DOB is not a word but
if it were a word like admin then of course this would look something like this even though in the database I know I would not have a capital A so we're gonna say capital DOB now this will
accept the attribute you can call it attribute or we could just keep it simple and say date of birth since we know that's going to be so then we can say this we can actually write right
into attributes and attributes is in our model and attributes is an actual array so we'll say date of birth is actually going to be and then what we could do is say carbon and then of course we'll
import carbon again use carbon carbon and then with us use parse let's let carbon kind of figure out what we want so go ahead and parse the date of birth alright let's run our test again and see
what we get now there we go now we get green so that allows us to do something like so so back in my authors test I know this is May 18 1988 so we can actually grab this and
say all right just to confirm that the correct date was actually parsed let's assert equals and let's put it in a different format let me maybe say 1980 8/14/2012 as the day so lowercase D and
then we need a month so lowercase M let's run our test and see what we get sure enough we still properly are able to format that date so that is the way that you would be able to handle a date
in our database so this is the proper way of testing it where we are asserting that first that we are getting that carbon instance and then just as a reassurance test you can reformat it to
a different format just to make sure that the date got properly parsed so that would be the way to do it so we'll wrap it up for this lesson let me go ahead and do some commits to push up to
github as we've done before and so let's see here let's start with our book what changes do we make here well we added this new path so let's go ahead and make a commit for that and said adds new path
to books resource alright what else do we have here why don't we commit all the author stuff together so we know that we know it's going to be our test so let's just say as author resource so then we
have the books controller let's go ahead and add that along with the test and the fact that we renamed the test work in progress for books resource and finally let's go ahead and
create that authors table now we probably should have put those together that's okay so now that we have that setup we can just hit push origin right here and now if we
head back and we hit refresh we should be able to see all of our commits and so there it is so we have successfully added four more things now of course we are nowhere near done with this we also
need to hook up these new authors into our books so in the next lesson we're going to be talking about how to combine these two new resources that we have
First of, let’s post our project up to Github and then start by implementing the delete sequence for a book. Then let’s explore how to parse a date and return a Carbon instance. Finally, let’s create a path helper for our book model and refactor.
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.