Test Driven Laravel
Lección 3 de 6 · Test Driven Laravel – e03 – Implementing a firstOrCreate Author Record With TDD · Educación
Test Driven Laravel – e03 – Implementing a firstOrCreate Author Record With TDD
welcome back so this is part three of the test-driven level course that we've been working on remember if you missed any of the first couple of episodes go
ahead and catch those first as all of these episodes have been building up to each other so if you missed the first couple you're not gonna quite understand what we're
gonna be working on in this one before I continue go ahead and hit the subscribe button and hit the bell button that way you're the first to know about any new episodes for this course or any of the
other courses that we have here at kotor state so let's dive right to it first thing I want to do is I want to go ahead and run my tests and everything is green an important concept about test-driven
development is that you always want to go from Green State to Green State you never want to start a refactor in a red state so we are at a green state so we can go on to the next section of course
any time we make a change to a test that will make it fail then we change our code and we come back and rerun our tests and if we did everything right obviously we're back to green and then
we do that over and over and over and over so with all of that yes we are at Green let's go ahead and go on to the next section that I want to work on so we worked on the authors section here in
the previous episode however if we go back to the book reservation right now an author is just a string right and that's not quite the functionality that we want what we would really want is if
they give us a string then maybe what I need to do is check in the database if that author exists and then I can either create one or I can go ahead and assign the correct author ID for that author
now there's a couple of different ways that we can do this right because we could theoretically have a list of authors think of the UI for a second we could theoretically have a drop-down
menu with all of the available authors that we have and put those in the database but as you can imagine this list would be quite large and so that's probably not what we really want
I think the expected behavior would be something where we have some predictive text and perhaps when they start typing into the box maybe we can guess what the author is going to be
based off of things that we have in our database and if not we can go ahead and default to just a regular author so that's quite good I do like that idea I'm gonna keep going with it what we
have here is simply a way for us to add an author manually and now we can actually get rid of without exception handling and I believe our tests will still be green and yes they are great so
how do we start to even tackle this how about we start it with a fresh test and then we'll have to go back and probably modify some of these tests and some of the assertions that we are doing so
let's just say a new author is automatically added all right so a quick point here this new author is technically inside the book management test so should that be in the book
management test or should that be in the author test well I can make arguments for either one but here's the way that I see it it's gonna live in the book because the endpoint that we're gonna
hit is the endpoint for books if we're going to hit the end point for author then I think it would make more sense that that test would actually live in our author management test but because
we are gonna hit this post inside the books resource and the books resource is the one that's supposed to trigger this ad or give me an ID type of functionality then I think it's better
suited for the book test so what could we possibly do here well we do have this let's go ahead and do that and what we really want to assert is the following let's assert count of one but not in my
books but rather in my author so an app author if we grab all of them then I really expect to have a count of one and the way that that would kind of work is right now we do have this date of birth
which is required so we are gonna have to do a little bit of finagling to make this work but here's the deal if we were to go ahead and hit an author with that then I expect that to end up in my
author yes of course we're gonna have a book that gets added so what we could do is another assertion for this let's go and grab that book save it to variable say book give me your first one and what
I want to assert is the following this assert equals and what I want to cert is that the ID of the author column ends up being ID of one so we could say grab that book variable and let's go ahead
and grab the book ID you can either grab the book ID eventually we're gonna have a relationship for that so we'll go ahead and add that but for now let's just compare IDs where did I get this ID
from well if we want to keep it the same we can always say all right go ahead and grab the author and let's just grab the first author in our database and then instead of using a hard-coded one let's
go ahead and use author ID and either one would work obviously we know our database is clean before every test so we can kind of predict what the IDS are going to be but if you want to be really
really explicit that's one good way to do it all right so let's go ahead and run this test and let's work through all of the errors and see if we can implement this in our project so the
very first thing we get is of course we have a size match of zero so right off the bat this right here is already failing so we got all the way through this and we were not getting any errors
now that's because we're really not testing anything on this end right here yes truthfully if you were to add this it would work so we're gonna have to work a little bit through our code to
make that actually work so let's think about this for a second so we are hitting author right an author right now is a string but what we're really trying to grab is the author ID and actually I
made a mistake here this is not book ID this is author ID there we go so what we could do is maybe use levels helper to help us convert this author into an author ID let's see what that would look
like let's go to the author model and let's create a new attribute public function set author attribute and of course that's going to give us our author and let's see what we could
do here how about this let's say this attributes author ID what if we try to fetch that author and we said give me the first or creates so this is a really nice little helper that will figure out
if something is inside our database and it will either return the object back to us or it will just create it and then return the object back to us so this will accept an array and we could say
well the name is gonna be author now of course this is going to fail for a couple of different reasons but we're just going along with the code here and kind of letting the test guide our way
through it so we're still getting the same error over here we haven't really quite changed anything let's do something let's actually flip these assertions that way I think our errors
gonna change and sure enough what we get here is that we're trying to get the ID of something that doesn't exist because our author actually didn't get created and the reason for that really is the
fact that that date of birth is still required right if you remember the date of birth is not nullable so this should fail altogether so let's go ahead and disable exception handling again and
let's see if we can get this error a little bit better for us and nope that's not gonna help us all right so when you have cases like this you kind of have to work through the errors slightly
different yes we could change our test until we can get the correct error but I think ultimately what we are seeing here is just simply that the author is not being created so of course that's why
everything is failing but we're not quite getting that error so what do we do in these cases now what we could do is something called drop down a level this is what would sort of happen when
you are testing too high up in terms of layers right we are testing too high and so we're having issues getting even the correct error and the reason for that again is just that we are testing too
high so we are testing right now as a feature test and a feature test kind of goes through a route and does what you think is should do we're still struggling to kind of even
get an air out of this test and we know there is an underlying error but we just can't quite see it so there's a whole nother level of test which is a unit test so now a unit test as the name
would suggest basically is the lowest level where we are actually interacting with classes and objects directly so let me show you a little bit of that and let's see if we can get the correct
error out of this so let's go ahead and refactor this one this is just the one that ships with Larry Bell's example that test let's just call it author test let's go ahead and refactor the test and
let's open it up so how do we write these unit tests well we do write them slightly different obviously we still have the same everything up here but how about this let's use refresh database
we're gonna need that and let's say a date of birth is nullable so let's say this if we grabbed an author author and let's say first or create and let's just pass in a name of John Doe so what do we
expect here well we expect author so we can just say assert count of 1 in the author's table that's kind of what we've been testing all along right we are making sure that in fact we are adding
an actual author to our database all right so let's see if we can change the error now this was running that one test sure enough now we are seeing that null constraint violation now we kind of knew
that we had an issue when we kept trying to change this test over and over to try to get an error that made sense and we weren't able to this test here will really never be able to reach down into
the lower level so then we have to drop and test at a lower level so with that now with this test then of course we realized that well the date of birth is knowable let's change the name of this
test actually to maybe something a little bit more related to the fact that there's a name only so let's just say only name is required to create an author how about
that so a little bit more about what we're actually testing and not about what we're not testing alright so how do we make this pass so let's go to my create authors table and this date of
birth let's go ahead and make that nullable all right let's run that test again and now we get green okay so we were able to solve that and now this is under test so an important point that
I'm trying to get across here is the following yes I knew that the error for this was the fact that it was not knowable we've kind of known that all along but if I just simply went into my
migration and just added that in and I didn't cover it with a test and I didn't get the error then we didn't do the steps of TDD and the steps of TDD is we start with a green test we change the
test and we make it fail and then we need to change the code on a failed test to turn it into green so we did follow the steps to add this line I couldn't justify just adding this end because I
didn't have a test that told me so that told me hey this is not right you need to make sure that that is knowable so all right so with that being said let's jump back up a level and let's try to
run this test now and let's see what we get and so now we're getting that we're trying to get an ID of a property of an on object now we know that sort of the next level of this is the fact that
there is no author underscore ID in our book and we know that for a fact so how about we drop down a level again and try to fix that error PHP artisan we do need to make a new test and this is gonna be
my book test now you can also pass in the dash dash units and what that will do is actually create a unit test as opposed to just a default feature test and if we go back here now we do see
here that I have the book test alright so what do we need here let's add a new test and say an author ID is recorded okay so let's check this out let's do either remember we are actually creating
right at the very root level of our project we are not going through an endpoint we're reaching in grabbing the model and creating something and this is what
makes this a unit test otherwise we would still be doing a feature test so we're reaching in and really grabbing the stuff in the database and manipulating it so what's the first
thing we do we do need a title and we could use faker even for this if we wanted to I'm just gonna go ahead and maybe say cool title and what else is required let me go to my create books
table migration and we need a title and yes an author is required right and right now we haven't refactor our test of course that is still required the next thing is I want an author
underscore ID and we could set that equal to one just for now because I'm only interested in the fact that I want this to exist so then we could say this assert count again of one if I grab all
of my books whoops I made a mistake there again it is not author its book all right we're making sure that we're grabbing a book this is our book test all right
sorry about that all right so now let's run this test and let's see what we get so now we get there's no such table as books of course we know this error very well let's go ahead and use refresh
database all right let's try that again and so now we get the error that we wanted and we knew that there was no column by the name of author ID so now let's go ahead and add that in so I'll
go in here into my migration and no I'm not gonna erase the author just yet all right so I'm gonna say table unsigned big integer is gonna be author underscore ID all right let's go back
here and run that test and now we get the failure that we're looking for we get a constraint violation on books author author obviously is no longer what we're going to be using so now with
a failing test I'll go ahead and delete this line and let's run that test one more time and sure enough now we are passing all right let's do this again let's go back to the book test and run
this a new author is automatically added test and see what we get from that one now we get a different error altogether let's see what we have to solve here so it says that there is no column named
author of course we know that so let's go back here and of course I'm hitting an endpoint with the name of author but we know that that's not quite the correct thing rather sure we're
going to hit it with an author but of course we do at some point one to convert this so let's check out the controller very quick let's go to the book controller and let's go right here
right at the store method we are validating the requests and of course we do have an author that is being passed in and it is required so that we are passing into our database but at some
point we kind of need to interject this a little bit and change it so what I can actually do is change this over to author ID is gonna be required and yes I'm gonna get a string at first but if
we go to the book model oh it looks like I put that in the wrong place I think I put it in the author model alright so I'm gonna grab this if you remember I wrote this a little bit earlier in this
episode so let me go ahead and cut that out of the author and let's put that inside the book model that's the one that it actually needs it so there we go so we're going to set the author
attribute and the one that we want to set is actually the author ID attribute and so we're gonna make sure that we convert this string author into the correct author all right let's run the
test again and let's see what we get all right so now we're getting a validation exception alright so we change the error that's good so now let's go back to the books controller and author ID is
required so why is it giving us an error alright so let me go to my book management test and right now we are still passing in author but what we really need is author underscore ID and
I think that that will fix this error that we're having right now and let's see all right so we got a new air great so now we are failed to assert that that matches one all right so I think that
this is just an error in our actual test let's go to book management test line 100 book management test line 100 all right so let's check out what we're getting for this book so if we said
Diane dump go ahead and give me the book author underscore ID let's see what we're getting there so it looks like we're getting an error on author ID and what we're actually
getting is just this big string we surely must have made a mistake in our implementation let's go back and check out that setter that we made inside the book model and let's see alright I see
what the problem is so right now we are setting this whole thing to the entire object if we wrap this in a parenthesis all we really need is just the ID we don't need the whole thing the whole
thing gets created on its own so if we run that again now we get one so now we could go back to our test and in the book management test we can delete that line and let's run this test and there
we go finally now this was a big refactor right there was a lot going on we had to go in we had to come back out we made a couple of mistakes here and there but through all of this the test
was the one that was guiding our code and that's kind of the point that I was trying to make I just want you to see sort of the flow and how it goes when you're writing tests yes at first is a
little frustrating to not be able to just go in and change your code and make the test pass you need to let the errors be the ones that tell you what you need to write that's a very important lesson
in TDD so let's go ahead and run the whole suite and I bet you everything is gonna blow up now so this is another common thing so yes our actual one test we were running we got that passing but
now we have a bunch of different failures right and we have failures in different tests and now we need to make sort of the same changes to our other tests so let's do one at a time a book
can be added to the library our very basic test let's go ahead and just run a book can be added to the library all right so we're getting the actual zero matches size one back to phpstorm and I
believe it's the first one here yep a book can be added to the library well I could tell you right away the problem is probably the fact that this is no longer author but I think we got a good
opportunity here for a quick refactor and it is the following we have this array and I can actually copy it over and over we have this array this happens a lot in tests you'll have
this array of valid data well we would consider a full array that passes every validation and everything's good with it but we keep repeating it over and over we make small tweaks to it
but ultimately it's the same array so what I actually like to do is extract this into its own method and let's just call it data you could call it just about anything you like I'll just call
it data so let's refactor that so now down here we have this new private function for data let's go ahead and clean this up we don't need that and all it does it returns that data so it
returns an array so if we go back up to our tests to make this test pass then all we need to do is make sure that this is author underscore ID and I think that will work and yes it does so that test
is passing again let's go ahead and run the whole suite and now we have two failures right we see here failures too and the next one is an author is required all right so this was when we
were testing out our validation so let's check this out an author is required so this array here is sort of the same array but only author is omitted so perhaps what we could do is use a PHP
function called array merge now the way a rape merge works is it takes the first array and it tries to merge it with the second array and third array and fourth and so on and so forth now if there are
any duplicate keys right the key in the latter part is the one that's gonna take precedence so if we said this data that's our first array but then as a second array we overrode the author
underscore ID and set that equal to an empty string then yes this will have an author ID a valid one but then we're gonna go ahead and pass a second array where that one is actually set equal to
an empty string so that one is actually the one that's gonna end up being posted to our endpoint so all right let's go ahead and run this again we are asserting that it is missing error for
and we need to be asserting that it's error author underscore ID there we go all right let's run the suite again all right so we have one more failure a book can be deleted all right let's see how
we can fix that one let's go back here a book can be updated a book can be deleted so there we go so of course we get the same issue we had here before let's change this over
to our new data method and I think that might just fix it all right so now we have one more in a book can be updated and it looks like this one is in line 54 so let's scroll up the line 54 and there
it is so we are grabbing a book but we are not creating the book because again we have the same exact error let's change this over to this data using our new helper
and let's see what we get all right so we're still getting an error there and of course it is that our actual data is not being updated and I bet you it has to do with the fact that we're still
passing in author instead of author underscore ID so let's go ahead and fix that now and now of course we are failing to assert that new author is what we got back now remember before
when we passed a new author then of course we were getting back new author but now what we're really getting is just an ID so we can change this test over to just being an ID of one but is
it one or should it be two let's check this out this is still gonna fail it fails because it's no meaning that there's nothing there and the air has to do with this right here
we're grabbing the author but again we need to be grabbing the author ID it's still gonna fail because it's really gonna be two so think about this for a second when we hit the first endpoint
we're gonna create an author with the particular string that we passed in then we're gonna hit that point again and that author is not going to match the author we already have so it's actually
going to create a new author in the database for us and return the ID for that person so that one is actually going to be two so that should actually make our test green again and
if we run the entire suite we are back to green so with that we've completed the full circle we've been able to implement that the book actually creates an author if it doesn't exist now again
this whole flow it is a lot faster in real life we just spent a lot of time talking about why we're doing what we're doing but in real life this would go much much faster at this stage yes we do
need some relationships we still don't have any relationships because really from my book I should be able to just fetch my author but of course that's going to be a lesson for another day
where we start to put together these two things and we start to create those relationships but for today we're gonna wrap it up with that and don't forget to subscribe if you have any questions go
ahead and drop them in the comments and I'll do my best to answer those for you go ahead and implement this in your project and when you're ready let's move on to the next lesson
Let’s dig through the code and find a way to create authors on the fly for our books. The idea being that a user could type into a “predictive” box in the UI and get a filtered author’s list.
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.