Copy Like Tests for Unlike0:00
Now, the logic for unliking something is essentially the direct opposite of the logic for liking something. So we could actually copy and paste what we did for liking and then just flip it to match unliking. We can save ourselves a fair bit of time. Let's copy our store test and let's rename it to destroy test. Of course, I've said it before, I'll say it again, be careful copying and pasting. It's easy to miss things and cause bugs that you don't realise exist. So go through the file line by line and make sure you've not missed anything by accident. For example, in our requiresAuthentication test, we'll need to change post to delete.
So go through the file line by line and make sure you've not missed anything by accident. For example, in our requiresAuthentication test, we'll need to change post to delete. We'll want to go to likes.destroy and we can still assert a redirect to our login route. You may think, well, why don't we just pass the like ID to the destroy method now because we have a like in the database. I'm going to stick with polymorphic parameters just because I don't want to have to start looking up likes and then passing them down to the front end and managing all that side of things. Instead, I'll just allow the server to do that lookup as and when needed if you are liking or unliking something.
Add Destroy Route1:07
Instead, I'll just allow the server to do that lookup as and when needed if you are liking or unliking something. Let's run this test. It fails. There is no root likes.destroy. Let's go to web.php. We can duplicate this line here. We'll want to make a DELETE endpoint with the same URL that goes to the destroy method and of course it will be likes.destroy. Let's rerun the test and it passes.
and of course it will be likes.destroy. Let's rerun the test and it passes. We'll now update our second test to make sure that we have our golden path sorted. So it allows unliking a likable. In order to unlike something, we'll need a like in place. So we'll say $like equals like factory for the given user and also for the given likable, we'll need to make sure we've defined the relationship name there as a second parameter and then we'll call create. We act as the user. We still want to check that the redirect back will work, so we'll leave that in place.
We act as the User. We still want to check that the redirect back will work, so we'll leave that in place. We'll make a delete request to likes.destroy, passing the morph class and the ID. We'll assert the redirect back to dashboard. We want to make sure nothing is in the database afterwards. So let's say assertDatabaseEmpty on the likes table. And then we'd expect the likable likes count to be zero because it should have been set to one and it's now set to zero. I actually think that's going to cause an exception. It's going to throw a problem, but we'll come to that as and when.
Implement Unlike Controller Logic2:28
I actually think that's going to cause an exception. It's going to throw a problem, but we'll come to that as and when. Let's run the test. And it says that we received a 200 response. So if we jump into the LikeController, I think the first thing I'm going to do is just clean this file up a little. We don't need all of these extra methods. So I can get rid of create and index and show. I can get rid of edit. Let's just remove these entirely and get rid of update as well.
I can get rid of edit. Let's just remove these entirely and get rid of update as well. And we should just be left with destroy and store. I want the same logic inside the two different end points. So request type and ID should be the root parameters. There we go. And then I'll copy this logic and we'll just update as necessary. We can paste it into destroy like. So well, we still want to find the likable using our little helper method that we created. We'll want to authorize to delete the like.
So well, we still want to find the likable using our little helper method that we created. We'll want to authorize to delete the like. I'm going to comment that out just for now because we have a test that deals with that later on. And then rather than creating any resource, I actually want to limit the likes to those that belong to the given user. So where it belongs to request->user(), and then I can just call the delete method to remove those from the database. And we want to decrement the likes_count column. Okay.
Fix Unsigned Decrement Bug3:48
And we want to decrement the likes count column. Okay. Let's now rerun that test. It fails with a 500 error and I'm pretty sure this is to do with, yeah, this line here as I talked about before. Let's just run this without exception handling so we can see what the actual problem is. Yeah, numeric value out of range. Let me explain what's going on. If we go to our post migration, remember we added this like_counts column and we set it to an unsigned bigInteger.
If we go to our Post migration, remember we added this likes column and we set it to an unsignedBigInteger. In case you were unaware, if a column is unsigned, you're basically guaranteeing that it will never go below zero. And that makes sense for likes. We are never going to have minus one likes or minus 10 likes. It will always be a minimum of zero. But in our factories for our tests, when we create a Post and a Comment, the likes column is set to zero by default. Which means that when we try to decrement the column, we end up going to minus one.
is set to zero by default. Which means that when we try to decrement the column, we end up going to minus one. To fix that, we could just update the factory that we create here. So inside the state array, let's say likes_count is by default already set to one. There is already a like in there. Let's remove without exception handling and rerun the tests and they both pass. Our next test states that it prevents liking something you already liked. Well, we want to prevent unliking something you haven't liked. We can get rid of our like factory, but we still need a model that could be liked. So let's say Post factory create will act as a random User, User factory create, and
Update Policy Authorization5:19
We can get rid of our like factory, but we still need a model that could be liked. So let's say postFactory create will act as a random User, userFactory create, and we'll make a delete request to likes.destroy passing some parameters that would actually work. But of course, because we've never liked this likable, we get a forbidden response. Run the test and it fails. Let's jump into our LikeController. We'll uncomment this authorize line and let's jump into the policy. Again I'm going to clean this up a little. We don't need forceDelete or restore.
Again I'm going to clean this up a little. We don't need forceDelete or restore. We also don't need the update method. We do need create obviously, but we don't need view or viewAny, so we can remove those as well. And we're just left with create and delete. Again I'll copy the logic here and paste it below. And currently it's checking for a Like model. It's assuming that we're going to be passing a Like model in, but as I already mentioned, we're passing polymorphic parameters in instead.
It's assuming that we're going to be passing a Like model in, but as I already mentioned, we're passing polymorphic parameters in instead. So I'm going to accept a model instance, which is the likable. And all of the squiggly lines start to disappear. Okay, this logic here is still correct. We only want to be able to unlike a Post or a Comment. However, we need to flip the logic here on line 35. We'll change it to exists. So you are allowed to delete a Like essentially if you have a Like. You can't delete a Like you haven't made.
So you are allowed to delete a like essentially if you have a like. You can't delete a like you haven't made. Let's rerun that test and it passes. Our next test is easy to update. It only allows unliking supported models. We go to delete. We'll say likes.destroy. We're attempting to unlike a User and it should already pass by way of the fact that we copy and pasted lines 31 through 33 here. Okay.
and pasted lines 31 through 33 here. Okay. Our final test, it throws a 404 if the type is unsupported, but we'll make a DELETE request to likes.destroy, passing the string foo, and we assert that it's not found. Again, it already passes by way of the fact that we have this helper method that we added. And of course in destroy, we're already using that helper method. So here we go. That was pretty straightforward. Hey, let's run all of the tests in this file. Okay.
Resolve Project Test Failures7:38
Hey, let's run all of the tests in this file. Okay. Six tests passing. And then I'm going to go ahead and run all of the tests in our project. So that's 102 tests. And it looks like we've got a few breakages. Let's take a look at what those are. So here are the four failing tests. It passes post to the view, is failing because the abbreviate method on number expects an int or a float and we pass null.
It passes post to the view, is failing because the abbreviate method on number expects an int or a float and we pass null. So let's take a look inside PostResource here, which is where the error is actually coming from. Yeah, we added this just a few episodes ago. So likesCount must be set to null. And I assume this is actually because on the factory, yeah, we never defined the likes_count column. We'll set that to 0 by default and we'll add the same thing to our CommentFactory as well.
We'll set that to zero by default and we'll add the same thing to our CommentFactory as well. Essentially this is caused because a factory doesn't reload itself from the database after it's created. So any columns you don't define in a factory's definition just won't exist on the model unless you pull from the database again, which we don't do in our tests. But with this update, if we rerun those tests, they now pass. And I would assume if we rerun all of the tests inside our project, give me just a moment. There we go. Hopefully they're all passing.
There we go. Hopefully they're all passing. Yeah, there we go. Happy days. So we have a clean green slate to work from where we know that the backend logic is in place for being able to like or unlike a Post or Comment. The only thing left to do is wire it up to the front end. Let's do it in the next episode.
