Lots of different ways to test record creation
Joel Clermont (00:01):
Welcome to No compromises, a peek into the mind of two old web devs who have seen some things. This is Joel.
Aaron Saray (00:08):
And this is Aaron.
Joel Clermont (00:15):
One of the topics we seem to keep coming back to is testing. We like testing. Right, Aaron?
Aaron Saray (00:21):
Yeah.
Joel Clermont (00:21):
I mean, it makes us feel good about ourselves. I think one of the things that's interesting about it as a topic, kind of like application code, there's a lot of different ways to do something. And testing is sort of that same bucket, right? Testing is code so there's a lot of different ways to test code. And we've kind of settled into some patterns over the years based on experience, things that have burned us. Just kind of the scar tissue you build up over the years of writing code and finding ways that don't work. I'm going to zero in on one particular thing. So let's get a little bit nerdy here. I mean, that's why you're listening, right? You like code, I don't know. How do you test... Let's talk about record creation. There's a few different ways we've done this and I'll kind of start with maybe the naive way. We could see what we've done to improve it and how it's actually an improvement. Let's say you're creating a product. I don't know, is that a good generic noun that we're going to create here?
Aaron Saray (01:24):
Yeah.
Joel Clermont (01:24):
Create a product. How do you test it was created? Well, maybe get the first one back and assert that the properties were saved the way you think. One piece of context here, because some people might be like, "Well, how would that work?" In our approach, we're kind of starting with the most empty database possible for each test, right? So even creating a user to post to the product endpoint or whatever, we'll create that as part of the test. So we don't have to worry like, "Oh, there's going to be like 50 other products in here." Just hearing me describe that, Aaron, why do you think that might not be a good approach? Because I know if I submit a PR to you and I'm doing that, you're going to have a comment. So share that comment with me.
Aaron Saray (02:08):
Well, I think the first thing to note is you did set it up properly saying there is nothing else in a database so we can still use positional type retrievals. You know, like first or latest or whatever, so I get that. It might be different if you have like three or four different blog comments or products or whatever and you're creating it. You might do the latest one, try to retrieve that and verify it. But I think the real question is like, "Why is it bad to just use something like first? Versus the other options which we have are firstOrFail or sole." And what I think is, the reason why we reach for those is because there's one less step inside of our testing them and then the error messages are, "If it does fail, it's going to be much more clear." If you think about it, if you go and try to create something, it doesn't create it but it gives you a success message with your response. When you go first, it's going to return null instead of that model that wasn't created. So the first thing you really have to do is check to make sure it's an instance of the model or not null. And if you don't do that, then at least you start checking properties and stuff. But then you're going to get an error message like saying like, "Can't find title on null," or whatever. And that's kind of like a really weird sort of message to get in the middle of your test. Whereas, if you do firstOrFail or especially sole, which is great, sole is almost a better one if you're just creating one thing. Be a way to say, "First of all, I want the thing back that was created. Second, there're better not be more than one." But you know there might be times when maybe there's some cases where you allow many and there's other ones where you just allow one or whatever. You get that all built in right away as you retrieve the model. So it's not going to be null and it's also going to throw an exception if it's not found or if there's more than one. Those exceptions are a little bit more logical for you. You'll understand like, "Oh, I've tried to retrieve just a sole one but there's more than one," versus, "I can't read this property on null." There's one less step to try to troubleshoot that.
Joel Clermont (04:09):
Yeah. And I want to just kind of comment a little bit on the use of sole, S-O-L-E. Because when that was added to Laravel, and it wasn't that long ago I don't think. I was like, "What would I use this for?" I think some of the sample use cases given in application code didn't really resonate with me but testing is where it really did. Because that scenario you gave where like, "This thing should only be creating one product. If there's two products in the table, something went wrong." Like, that test should just fail, it should just stop. I don't think I've ever yet reached for sole as an eloquent method in a application code that I can think of but I do use it with some frequency in test code. I like what you said about the error message because the initial reaction might be, well, if it says, "Can't get property title on null," I know what that means and we do. Over the years we've seen that error enough times, but like why-
Aaron Saray (05:10):
I've never seen that error.
Joel Clermont (05:12):
You've never seen that error? Get out of here.
Aaron Saray (05:15):
I've never made that mistake.
Joel Clermont (05:16):
Right. Well, duh, we know what that is. The thing was null, that's where to look in the test. But why even force your brain to go through that pathway? If you can just catch that error, stop the test sooner, I think that's a better solution. Maybe one other little thing to put in there. Because you mentioned use of the word or the eloquent method latest. That's another thing too, where if we're testing and maybe more than one thing is getting created or one thing is getting created in addition to some other things that we set up in that particular tests setup method, then sorting becomes important too. Because if you don't have an explicit sort, you're kind of relying on the way the database retrieves records to bring things back in the logical order you think they should be in. I just think that was worth calling out too. Because that's another one that has burned me. If you don't sort it explicitly or if you rely on ordering by ID or something or created_at, if you're not explicit about that, it may change down the road and then you'll have tests fail. And it might even fail like one out of a hundred times and you're like, "What is going on here?"
Aaron Saray (06:28):
Okay. It's good you mentioned latest because first of all, when you talk about that too by default I think it's sorted on created_at. If so that's going to be a problem in your test because whatever maybe you've seeded in and also maybe what you've written all might have happened within that one second. So it's like, "Well, which one does it pick?" So make sure you pass in like ID or something. And that's if you have an auto incrementing ID. The other thing about that, one of the other tip that we've used which I am pretty certain is a good idea otherwise I wouldn't be sharing it, but I'm not 100% sold. Is, especially when I have auto incrementing IDs, sometimes if I have a very complex set of data I might create a factory of models up until the point that I'm going to create one with my endpoint with a specific ID, like 99, so that I know that the next one created will be 100. That way maybe if there's a bunch created... So there's like 101, 102, 104, or whatever, I can point to the specific ones that I'm reasonably certain will be those IDs. Versus if you're depending on how your database is set up, some IDs might be reused or they might... There's other little tips and tricks you can do there as well to retrieve the thing. And of course this is all speculative on a little bit of us saying that the return of this endpoint is not like a JSON API or something like that.
Joel Clermont (07:53):
Oh, sure.
Aaron Saray (07:53):
Because a lot of times those will send back the ID. Whereas your web-based ones will just say, "Yeah, you made it buddy."
Joel Clermont (07:59):
Right. Yeah, good point.
If I asked you, Aaron, what your shoe size is, would you know the answer to that question?
Aaron Saray (08:14):
Yep.
Joel Clermont (08:15):
You're not going to share with us?
Aaron Saray (08:16):
Nope.
Joel Clermont (08:16):
Too personal?
Aaron Saray (08:18):
Yep.
Joel Clermont (08:18):
Okay. Now, if I were to ask you what size shoelaces do you use, would you know that?
Aaron Saray (08:29):
Oh, no. I would not. I guess I didn't realize there's differences. But it makes sense because there's like high tops and then there's low tops and then there's no shoelaces. I don't actually wear shoes with shoelaces. No, they're not velcro. They're like leather sort of slip-ons. I'm thinking golashes, but that's not the right word.
Joel Clermont (08:52):
No, what you wear it sounds classy and golashes is for going out in the rain.
Aaron Saray (08:57):
Yeah. From my ankles down, I'm very classy.
Joel Clermont (09:02):
That's where you ought to be the classiest. Well, one of my kids... I think the cat or something chewed their shoelace, so we had to buy more shoelaces. And I was like, "Well, I'll go on Amazon. It's easy, I'm sure there'll be a size for kids versus adults." Oh, sweet child, that was not the case. There was like, "How many inches, how many eyelets on the shoe?" I ended up ordering like three different sizes to find out one that was correct. But I just thought like it's kind of... I mean, I know that's a kind of an odd problem to have but I think we need more awareness of shoelace sizes in our day-to-day life.
Aaron Saray (09:39):
Hmm. What were the size that you ended up on?
Joel Clermont (09:42):
I don't even remember, that's the problem.
Aaron Saray (09:44):
If you're looking for more tips for testing your Laravel applications, we can help.
Joel Clermont (09:55):
Head over to masteringlaravel.io and click on articles. We have a couple on testing there.