skip to content

Using tests for cleaner code with constant refactoring

Tests can help us clear the clutter out of our thinking — and our code — if we look at them a little differently. Cory Speisman teaches us how TDD can be a thinking tool.

Full Transcript

Click to toggle the visibility of the transcript

Captions provided by White Coat Captioning (https://whitecoatcaptioning.com/). Communication Access Realtime Translation (CART) is provided in order to facilitate communication accessibility and may not be a totally verbatim record of the proceedings.

JASON: Hello, everyone, and welcome to another episode of Learn With Jason. Today, we're going to be digging into one of my favorite things, which is coding better. This is something near and dear to my heart. I've spent a significant portion of my long career thinking about how do I write code in a way that gives me the least amount of pain when I maintain. Gives me the fastest path to adding new features that's least likely to cause me a bunch of headaches when I change it later, right. And I can say after 20 years, I'm still not very good at it. But, today, I'm bringing on a guest who is good at it, and who is going to give us some techniques and ideas on how we can do a better job of thinking through our code up front so that we can make better decisions and write code a little faster, more efficiently, and less painfully in the long run. So, please, welcome to the stage Cory. Cory, how are you doing?

CORY: Hey, I'm doing good, thank you. That's a very high promise to say that I also know well. I am also always fumbling through getting more maintainable code.

JASON: Yes, I mean, it's a constant point of pain. I think we all like, honestly, I feel like if you get to the point where you're not having any issues with your pain at all or any pain in your code at all, you're probably not (cutting out)

CORY: Totally. I feel I've spent the same 20 times, different paths and patterns.

JASON: Yes, exactly, right. I feel like if you really want to push the limits, you've got to take yourself out of your comfort zone and do something you've never done before, and every time you do something you've never done before, it's going to you're going to build it and it's going to be great.

CORY: Totally. That is kind of what prefaced this whole thing. Hey, let's talk about this. All stemmed from I watch your shows all the time. You do 500 things at the same time. I don't know how you do it. Like your coding challenge. I saw something

JASON: All right, before we talk too much about the code stuff, I want to talk about you for a minute. So, for folks who aren't familiar with you and your work, can you give us a background on who you are, what you're into, what you do, all that good info?

CORY: Totally. Hey, everybody, I'm Cory, I live outside of Washington, D.C., in Maryland, with my lovely wife, our 1 and a half year old, and a little puppy who's now like 7 years old. I work at Netflix on it's called the Cosmos platform. We are a team responsible for taking every single asset that has to get encoded for Netflix, whether it goes on to service, or, like, on the studio side of things, of like they are shooting video and, like, the dailies have to make it to like the editor bays and stuff like that. All of this streams through this orchestration platform called Cosmos. So the videos, the audio, the subtitles get encoded, so they can play on the correct devices, and all this has to get sent through like an orchestration platform to make sure that we are providing the right amount of compute, everything has like a good observability tool for it. So, that's what I do.

JASON: Your description of your job just raised my blood pressure.

CORY: In a good way or a bad way?

JASON: Like, that stresses me out.

CORY: Yeah, very stressful.

JASON: That's a lot of pressure, man.

CORY: I for some reason, I feel not amazing pressure, and I think it's because I have such a good relationship with so, our users are engineers at Netflix. They all are like encoding engineers. So, I feel like the pressure is relieved by like having great relationships with them. And, like

JASON: Yeah.

CORY: This is kind of what we'll get into, quick feedback loops of like providing them solutions, talking to them, making sure that I am building the right things, and making sure that our solutions are providing the right level of, I don't know, abstraction for them, just like helps reduce friction.

JASON: Yeah. I mean, that's so, what's interesting is that we set out to talk about how to write code better, and the very first thing that comes up is the relationship with the people you're writing code for.

CORY: Yeah. Somehow all ties together.

JASON: It just goes to prove that no matter how much we like to think that engineers aren't people aren't people people, we can say I'm not a people person, but, ultimately, if we aren't communicating with the people that we're writing code for, no amount of technology is going to save us. Like, it is a communication job. It's a people it's a person job.

CORY: Yeah. I've never said this before, but I'm going to say this saying now, and I think engineering is very much a team sport.

JASON: Yes.

CORY: As long as everyone's playing the same sport, usually goes so much smoother.

JASON: Yes. And I had to highlight this comment here, because Angie Jones is in the chat, which is an honor, thank you, Angie, for spending some time with us today. I know how busy you are. But we said testing too many times, so like Beetlejuice, she appears. Okay. Let's talk a little about at a super high level, what you pitched to me is on the screen, Using Tests for Cleaner Code with Constant Refactoring. So, when we start to approach testing, when we start to approach cleaner code, when we start to approach constant refactoring, there are about 100 million ways that we could go in terms of how we approach those things. So, if you've got maybe an elevator pitch on, like, your world view or sort of what's the takeaway for us here, before we start getting into like the how and the specifics, but at a high level, what's your pitch? How do you make this easier?

CORY: I have an elevator pitch about testing and why we should be testing, and I think it will tie all those things together, which is the goal for us, for engineers, is to deliver software faster and more predictably. And maybe, like, the higher level goal is we all want to retire and be on a beach and, like, drinking pina coladas or something. But in order to get that, we have to ship good projects. And in order to do that, we have to put things in front of users. We have to do it as fast as possible, responsibly, of course, because that is what feeds that feedback cycle to learn and build. Like, you build something, it's somewhat correct. You put it in front of a user, you get feedback. Then you build the more correct thing and put that in front of users and get feedback. And that is really the goal. And in order to get to that goal, you need to have really, really easy code to reason about. And you need to be able to add features quickly, you need to be able to, like, maybe take away features. You just need to be able to manipulate and work in a codebase, so that, like, you can deliver things on a very quick iteration. Okay. So, in order to get that, you need to be refactoring your code constantly. I think, like, refactoring code is a lot like hygiene. It's not like something where I can sit on the couch and eat donuts for two to three years straight and be like, you know what, I'm going to call hygiene debt and just start all over again. It's like a constant thing. It's like a practice that you have to be into.

JASON: You just emerge from the pile of crumbs. Burn the couch. Get a new couch.

CORY: It's hygiene debt. I've done those.

JASON: Hygiene debt. You know, I think some of us during COVID maybe experimented with this approach of hygiene debt. But, no, I actually agree, because I think and when you first said refactoring, the thing that I always am worried about, right, is the balance between working on the project and working on the codebase. And because I think that I have a tendency to be like, you know, as an example, when I was younger, I had my toy cars. And I'd get my toy cars, and I never played with them. I arranged them, right. I would take them, put them in these elaborate scenes, then look at them, then put them all back in the box and I'd be done playing. And I never actually did the thing I set out to do. I spent my energy on the organization, the cleanup, and the specific tweaking, right. And I carried that into adulthood. Now when I write codebases, sometimes I get so into my file structure or the specifics of my abstractions or how I'm going to optimize for clean code or whatever, that I never actually get around to building features. I'm always rearranging the code to make it optimal for productivity. And it gets completely in the way of my productivity. I've joked that it's the most productive way to procrastinate that I've ever come across.

CORY: Totally. And I'm so guilty of that, too. So, the rest of the elevator pitch is I need to refactor, in order to refactor, I need that confidence. In order to have that confidence to make those refactors, to make sure I'm not breaking anything, I need my tests. But to your point of, like, the constant refactoring cycle to never actually deliver value, I think that's also where tests play a lot, of being like what is my goal here. What am I trying to accomplish for the user. Let me write a test for that. Let me implement the code to deliver on that. Let me ship that out. Okay, I have this behavior covered in my system now. Now I can go around and play with it. Now I can find the right abstractions or find the right implementation. And then next time, hopefully, you write another test that fails, you write that implementation, you do the same thing. Now your system is starting to grow and grow and things are starting to get complex. And that's when you can, like, also start to like mutate and manipulate and really find the right abstractions to use.

JASON: You just said something really interesting that I had not connected before that has me thinking. So, you were talking about tests, but the way that you talked about a test is you said what is my app supposed to do, what is my user trying to accomplish. And the test described that.

CORY: Yeah, totally.

JASON: Now, I have always thought about my tests as isolated checks for, like, code.

CORY: Right.

JASON: And I've never really thought of them as being full circle what's going on. It's always been more about, like, okay, I wrote this functioning to make sure this function works, so I write a test for it. Then the other trap I fall into because of that is in my refactoring, every time I change code I have to change tests, because my tests in my code are so tightly coupled.

CORY: Yeah, can't de couple those things.

JASON: If you're in the chat and guilty of that, give me an "F" in the chat, if this is you. Because, like, it's me.

CORY: Yeah, it is me, too. And the whole idea is like test your behavior, not your implementation details, and you'll see how that and we can hop into it soon. We can see, like, how that looks in practice, and how it enables you to refactor and make changes or, like, swap pieces in and out a lot easier.

JASON: Yeah. See, lots of Fs in the chat. I think a lot of people are as excited as I am to learn about this, because it's one of those things it's... like every bone in my body wants to be good at writing tests, and I want to do all the things that I can say, yeah, I'm really responsible, but it oftentimes feels like I'm spending so much energy on trying to do the right thing that I forget what the actual point of the thing I was building is in the first place. You know, like I said, I'm rearranging deck chairs. So, I'm really excited to learn some strategies that can help me do this better. So, you I just picked up from you maybe it makes sense to jump over into some code here?

CORY: Yeah, let's hop into it.

JASON: All right, let's do it. So, first and foremost, I'm going to do a shout out to our live captioner. So, we've got Ashly with us today from White Coat Captioning. Thank you so much, Ashly, for being here. And our captioning is available at this URL that's going to start scrolling across the bottom of the screen. Captioning is made possible by the support of our sponsor. We've got Tuple here today. Tuple is a paired programming tool. So, Cory and I are on a Tuple call right now, and that means that we can do things like draw on the screen, and we can say, hey, go check out Tuple. And if, you know, we need to, Cory can send me links. You know, throw confetti, all sorts of fun stuff, right. But Tuple's cool. If you do remote programming, you should definitely get into it. We're talking to Cory today, so let me throw a link to Cory into the chat. LinkedIn seems to be the place to be these days, so get up on the LinkedIn channel. You know, give a follow, give a whatever. Now I'm at the end of things I wanted to say, so Cory, if we want to get into things, what do we do first?

CORY: Let's open a terminal.

JASON: Open a terminal?

CORY: So, I have a side project.

JASON: Okay.

CORY: This is how I get up and running on a project, and I've never run it on anyone else's computer before, so I want to see if it works. If it doesn't work, I have a GitHub link we can do, but I'm so curious if this works. NPM create.

JASON: Folder first?

CORY: Yeah, go into a folder.

JASON: Okay, let me go into my GitHub folder. Is it going to create the folder for me?

CORY: It's going to create a new GitHub repo.

JASON: Create a repo in a new sorry, do I need to create a folder and in it

CORY: No, sorry, I should have given you better directions. It will create a directory for you. It should create our GitHub repo, it should create wherever we're going to deploy this thing to. I'm assuming Netlify. And it should set up like our GitHub actions to run our test suite and send off deploy preview URLs and run our end to end tests and stuff like that.

JASON: Nice, all right. Color me intrigued.

CORY: Let's see if this works. Don't be too intrigued yet, because I don't want to over promise. Okay, so, Krew with a "k." K r e w. No "e" at the end. At symbol latest. Name of the project.

JASON: Okay, we're going to call this... what is it, tests and cleaner code.

CORY: All right, let's see if this works.

JASON: It is doing the download thing.

CORY: Cool, and we wait.

JASON: All right, so...

CORY: So, let's do Astro.

JASON: Okay.

CORY: Go to Netlify.

JASON: And...

CORY: Let's do, yeah, the recommended.

JASON: Okay.

CORY: Oh, my gosh, my heart is beating so fast. Please work, please work.

JASON: So, this is like your own personal home rolled starter?

CORY: Yeah, I just got so tired of setting up my pipelines and stuff, and knowing how to upload environment secrets to GitHub and stuff.

JASON: Where's my Learn With Jason? I have too many. Way too many.

CORY: I can tell.

JASON: The problem is over the years, companies always forget to take me out of their org, so I'm just in everything.

CORY: Nice.

JASON: Here we go. Successfully authenticated, so back to the terminal.

CORY: If this works, I might jump up and down.

JASON: Okay, put it on my team. And dang. This is great.

CORY: Don't say that yet. Please, don't jinx us.

JASON: I believe in us. Successfully set up application. So, now we're going to go in and open this up.

CORY: This is amazing. Yeah, and we should have a Netlify URL up and running.

JASON: Okay, let me make sure I'm in. Get in this there we go. Little bigger for us.

CORY: I've been building software for 15 years, and it still never doesn't amaze me when I build something that actually works.

JASON: That's the magic, right.

CORY: Crazy.

JASON: Doesn't matter how many times you've done it. We just taught these rocks to think, and it's still a miracle every time.

CORY: Blows my mind. Holy crap, I actually did that.

JASON: So, this is great. All right. Let's take a peek in here what we've got. We've got a v test thing set up, playwright roll in. Testing stuff. We've got Astro, we've got the Netlify adapter. We have Netlify CLI yeah, so, we're in we're in great shape here.

CORY: It's very bare bones basics. As little opinionated as can be.

JASON: Should I get this extension, the playwright test?

CORY: I don't know.

JASON: If you don't use it, I'm going to skip it.

CORY: I don't use it. For what it's worth, I don't use VS Code. I'm scared to say it out loud. I use the Jet Brains IDs.

JASON: They are great. And for anybody interested in trying it, the webstorm now has a noncommercial license, so you can use it for free. One of the big blockers was always it was a paid IDE, which makes sense, because they are good, really good. But the, you know, if you didn't have the money to test it or you're doing side projects or whatever, now you can just use it. So shout out to Jet Brains for doing that.

CORY: Yeah. What's worth noting, let's hop into the Veet test config.

JASON: Veet test config is here.

CORY: So, this is just like a very bare bones config to say we're going to use JS DOM for our React tests and don't want to run our end to end tests when running integration end unit tests.

JASON: Got it.

CORY: Other than that, maybe a in the Astro config, we have the Netlify adapter.

JASON: Netlify and React setup.

CORY: Yep, and we should be good to start coding.

JASON: I'm ready.

CORY: All right. I'm going to hit you with my project idea.

JASON: Okay.

CORY: So, currently, me and my friends have taken on a very, very difficult task, and that task is to eat every single menu item at Cheesecake Factory.. I don't know if you've seen the menu at Cheesecake Factory, but it's vast.

JASON: Let's just take a look here. Let's see. 13 small plates and snacks, then appetizers, which are different. 25 of those. And then flatbread pizzas need their own category. Oh, my goodness, already tired. Seven different sandwiches. 14 salads. 40 lunch favorites, oh, my goodness. 21 specialties, 16 pastas, 11 seafood items. Combos. Oh, my God. Just keeps going.

CORY: We could be here until the end of the stream if we keep going.

JASON: Okay, so, your plan is to eat all of this. Also, I pity you for so many of these.

CORY: So far, it's been an amazing experience.

JASON: All right.

CORY: I'm not here to be a Cheesecake Factory stan, but there are some topnotch items on this menu that had I not gone through this whacky idea, I would have never explored into.

JASON: Hold on, do you have to eat all the

CORY: Yes, every single menu item we are eating.

JASON: Okay, so, you're going to do this until you're dead.

CORY: Yeah, it's a multi year endeavor, for sure.

JASON: Okay. All right.

CORY: Okay. Anyways, we want to do this. My idea is to create an application, where I can just track my progress, and I know which items I've eaten and which ones I haven't.

JASON: Okay.

CORY: Okay. Let's start coding. Actually, let me hit you with one other thing. I put together a little bit of almost user stories do you mind if I take over your screen real quick? Do you mind bringing up VS Code again?

JASON: Go ahead.

CORY: Creating a to do .md file.

JASON: Yeah.

CORY: Cool, all right.

JASON: You're in the writing mode. Now you have to get out of drawing mode and into clicking mode.

CORY: Thank you so much. I could have been here for a while. Okay. Yeah, I feel like this helps me stay focused and not like go off on tangents, which are like very clear what I would call MVP items. Once we get this, we have our MVP, we are good to put in front of our user. So, the first one is I want to see all the Cheesecake Factory menu items. So, let's do it.

JASON: All right, let's do it.

CORY: Let's go create a new directory called lib.

JASON: We doing that in the source?

CORY: Yep, sorry. Source, lib.

JASON: Okay.

CORY: Then in there maybe a new directory called menu.

JASON: Menu.

CORY: Okay, then let's do a menu service file. So, menu service.ts.

JASON: Okay.

CORY: And then let's do a menu service.test.ts.

JASON: Menu service.test.ts.

CORY: Awesome. Okay, let's write our first test. Let's take this approach as we're not thinking about the UI yet, and we're not thinking about like our inputs and our outputs. Not thinking about UI, not thinking about persistence, not thinking about external APIs, even though we know we're going to have to hit them. Let's just think about like our core system and the business logic around it. So, let's do a describe block and just say describe menu service. Cool. And then inside of there let's do

JASON: Sorry, been a while since I've done this. We're going to learn quickly how long it's been since I've written a test.

CORY: Oh, I love it.

JASON: Like this, right?

CORY: Exactly. Then it, description of the test. So say it gets

JASON: Loads all

CORY: So, let's de couple this idea from Cheesecake Factory right now. Let's just think of this as menu service, loads menu items.

JASON: Got it.

CORY: All right, cool. Now let's go to our menu service implementation. Is it possible to put the files next to each other?

JASON: It sure is.

CORY: I feel like I find this really helpful.

JASON: Okay, then we're going to need some kind of export function.

CORY: Yeah, let's do a class actually. Export class.

JASON: Menu. Service. And then that's like this, then we have a

CORY: Call this like a don't even need the constructor yet. Get items. Jason, delete that, we'll be fine. We're professionals here. Okay. Love it. Okay, so, it loads all menu items. So, now in our test let's create a new menu service.

JASON: In our test, we're going to create a new menu service. And do I want to do it in the it block?

CORY: For now, let's do that.

JASON: So, we'll call it menu service equals new menu service. I think I can just input that, good. So, what, we expect menu service get items.

CORY: Let's do to have length.

JASON: Is that right? Something is missing where these aren't completing.

CORY: We have to input everything from Veet test.

JASON: Oh, got it, okay. Let me do that real quick, so that it's

CORY: Describe

JASON: Oh, node test. Look at you.

CORY: We might have to manually type that in. Sends to pick up.

JASON: From vitest. Then describe it. And expect.

CORY: Run the test, it's going to fail.

JASON: So, we're here, run a new terminal. I'm going to NPM run test, right?

CORY: Yep. Sorry, let's install JS DOM, my bad. I set you up for that one, Jason, I apologize.

JASON: That's all right. It saved us, right. We didn't even have to test failed, watching for file changes, and it says wait, we screwed oh, I just wrote bad code. Didn't fail for the right reasons. It failed because I straight up didn't finish importing this code. So, now we have a real failure.

CORY: Okay.

JASON: And it says expected to have length but got zero.

CORY: Okay. Perfect. So, we know for this implementation we're going to reach out to the Cheesecake Factory API, which I actually have the API link in our todo.md file. We know eventually we're going to have to reach out to this, but let's not do it yet.

JASON: Oh, let's not do it yet.

CORY: But let's consider Cheesecake Factory as an input. And what I'm thinking about like good design patterns, I want to abstract away my inputs and outputs. And these are like, you could abstract away you could go as crazy to say I'm going to abstract away the UI stuff and hide that behind an interface. I'm not going to go that crazy.

JASON: Okay.

CORY: But I do think for something that's receiving data from a different source should get put behind an interface that gets put as an explicit dependency on our service.

JASON: Okay.

CORY: Okay. So, what does that look like, and then we can probably back up again, is let's in the same file, let's create an interface, like a TypeScript interface. And let's call this menu client. And we want this client to be responsible for getting menu items. So, let's call this like maybe get items, even though it's a little redundant. And we know this is going to call some, like, outside source. Which means it's going to be put behind a promise.

JASON: A promise? Oh, yeah, yeah, yeah, you're right. You're right. I knew that. I know honestly, I'm just like thrilled I'm getting anywhere near this. And now I'm making some assumptions here that we know it's going to come back as an array, which it might not. Do we do you want to do this in a different way?

CORY: Yeah, let's type this out. Let's say we know it's going to be an array of menu items.

JASON: It's not a function then, just a promise return?

CORY: It is going to be a function.

JASON: It is going to be a function, okay.

CORY: I think it will be a function that returns a promise, though.

JASON: Okay, I was getting close. Like that. There we go.

CORY: Yep.

JASON: Yeah, okay. This is going to return an array of menu items.

CORY: Yeah, and let's create that class, too.

JASON: So, then we need interface

CORY: Let's call this our model maybe. Where it's like an actual let's do class menu item.

JASON: Wait.

CORY: Perfect. Let's say this has a name for now. Keep it simple.

JASON: Okay, so, the name is... going to be something.

CORY: Yep, perfect. Maybe here let's do a constructor. So, when we construct these, we can assign the name.

JASON: Somebody remind me how classes work. Is that right?

CORY: Yep, exactly. Now it's going to take a name as an argument and this dot name equals the argument name. Perfect. Love it, okay, cool. So, now, our menu service needs to take this menu client as a dependency. So, this is where we create a constructor in our menu service.

JASON: Okay. Constructor, and it's going to be like a client.

CORY: Yep, exactly.

JASON: Okay.

CORY: This dot client equals client.

JASON: Do I need to can you do this.client or you need to

CORY: No, you need to I think you can say I forget what the syntax is. Private bar client or something like that. I don't know, anyways, let's do it the old school way and give it the type menu client.

JASON: Menu client, okay.

CORY: And the one on line 14, menu client.

JASON: Oh, got it. Got it. Okay.

CORY: So, now we know this menu service has a menu client, but we don't have any implementation of this menu client.

JASON: Right.

CORY: But we can still call the methods on it. So

JASON: We can? It's only in the interface, right?

CORY: Technically.

JASON: Fail?

CORY: So, that's going to fail, because it needs an implementation of menu client.

JASON: Right, okay.

CORY: So, let's create a fake implementation that's going to return a promise of menu items.

JASON: Okay.

CORY: And let's, for now yeah, let's do it there. Maybe call this menu client stub. Meaning you call it, it's just going to stud the response back.

JASON: Got it. Actually, I can just take this and return this straight in, right?

CORY: Yeah, might be helpful to do an implement implements menu client, just to have that type safety.

JASON: What a great idea. Now you're mad. Don't be mad at me.

CORY: No, that's good, right?

JASON: Not assignable. Oh! Type never is not oh, look at me go. Okay. So, if I just make this async, that should solve the problem, right?

CORY: Async. And let's do

JASON: But it's a never. Why is it a never?

CORY: That one I don't know. Menu client promise never... that's weird to me. Maybe just not getting called.

JASON: Who's to say?

CORY: Yeah, all right. Let's keep going.

JASON: TypeScript.

CORY: Seriously. Let's create some menu items.

JASON: Okay.

CORY: And return it.

JASON: And we had a name.

CORY: Or we can use like the the object oriented way of calling new menu item and use our constructor.

JASON: Oh, right, we can do that. So, let's do new menu I forgot we created this class already. Cheesecake. Then we need one more do we want to make it pass, like get six?

CORY: Yeah, or change our tests. No big deal either way. Love it, more cheesecake. Cheesy cheesecake.

JASON: What's one that makes me sad? When you get to pumpkin cheesecake on the menu, I get sad for you, but fix it with salted caramel cheesecake, because that's the good cheesecake. So, now theoretically this method will pass once we get it into this

CORY: Yep. It's not adhering to the contract yet fully, because it's not returning a promise, so let's wrap that all in a premise resolve.

JASON: Isn't this a promise? Doesn't this make it a promise?

CORY: Does it?

JASON: Yeah, I thought so.

CORY: All right, let's do it then.

JASON: It's saying now it's a promise of menu.

CORY: You're right. Yeah, yeah, cool. So, now in our tests we're going it have to export that menu client stub.

JASON: Got it. Export class menu item stub. And now I can

CORY: Drop that into there.

JASON: Make this a little bit bigger, so we can see what's going on. And we're going to say menu item stub no, client stub.

CORY: Yeah, it's all right. Cool.

JASON: We'll update that import, call this one actually, let's just call this one client. I have a feeling that's going to change over time. Then we can drop in our client. It makes TypeScript happy and when we save, our tests pass? No! Why not?

CORY: Do you have

JASON: We're not calling the thing.

CORY: Tests are great. They catch us from doing dumb stuff.

JASON: So, now it would be this.client.get items.

CORY: I think we'll have to wait this one, though.

JASON: Yes, we will? Yes, yes.

CORY: Yeah, that will be asynced. That will be awaited. And then our tests will also have to be awaited to get the response.

JASON: Right. And to do that I have to make this async here.

CORY: Yep, exactly.

JASON: Working with the tiniest possible code editor window, but we have a passing test, everyone.

CORY: There we go. That's one, baby.

JASON: Okay. So, the immediate first question I have is we know the length of our stub, but we don't know the length of the Cheesecake Factory menu, and theoretically, that could change at any time. So, how do you now this kind of poses the first actually, hold on. I'm guiding. Why don't you guide. What should we do next?

CORY: No, I love that question. That's a very, very valid question. And it's to say that this test, because we are creating that stub, is always going to have six.

JASON: Okay.

CORY: We are hard coding the response to that stub, but we created this contract that we know is going to have get items on it, and that get items is always going to return an array of menu items no matter what happens.

JASON: Okay.

CORY: So, if we wanted to plug this into the Cheesecake Factory one, we would create a Cheesecake Factory client, implement our menu client, do that fetch request, and then, like, merge that data to be an array of menu items.

JASON: Okay.

CORY: But let's not even go there yet, because we have something that's working for us right now.

JASON: Okay.

CORY: So,let hop into React land. And let's write a test for a React component.

JASON: Okay.

CORY: I should also mention that if this feels like a lot to test the fact that we are getting six items back, you are totally right, and your assumptions are 100% there. This is a lot. And if this was the end of this project, I would say this is like overkill. But this is going to be a project that lives on, it's going to have new requirements, new features and things like that.

JASON: After Cheesecake Factory, it's going to be Outback Steakhouse, Applebee's, et cetera. You're going to run the full gamut.

CORY: You're going to have all these different menus and APIs. REST, GraphQL, SOAP APIs.

JASON: You know one is going to be SOAP.

CORY: One is super cutting edge, GRPC and stuff. But we know we have our menu client, and that's our domain. Cheesecake Factory, that's their domain. However they want to implement their API is up to them.

JASON: Right. We're building a normalization interface in this menu item. Or client.

CORY: And we're keeping everything type safe. No matter what, we are going to be working with our types.

JASON: Got it, yeah. Okay. So, let's get into React land and we're going to do that in our components folder maybe?

CORY: I again, do you have like a hot take button, because I have plenty of hot takes.

JASON: You have a little fire button right there in your Tuple controls.

CORY: Hot take, I put this I keep everything in the same domain together in a directory. So, I would create a component directory in that menu directory.

JASON: Hold on I have one of these

CORY: Is that a record scratch?

JASON: I have a thing. I never push the buttons on it. Why not? Let's play.

CORY: Why not?

JASON: Okay, you would put your components right here in the menu service. How do you control the chaos in a thing like this? Do you have a naming convention?

CORY: No.

JASON: I do what I want?

CORY: I do what I want. If it feels good, I keep doing it. If it feels bad, I go and rename or refactor. Yeah, awesome tools, and they manage all this refactoring effort for you.

JASON: Honey badger TS. We do not care, we'll do whatever we want with this code and you can't stop it. What should we name this component?

CORY: Can I take another step back? This is the beauty of tests. We can rename our files, we can change the file structuring, and if our test suite still passes, we have that confidence that everything works.

JASON: Yes.

CORY: I think that's a beautiful thing. You're killing me over there.

JASON: Okay.

CORY: Let's create a components directory. Sorry, I have to focus. Let's create a components directory in our menu.

JASON: In our menu. Okay. Now we're getting wild.

CORY: Yeah. What if this becomes its own package one day, what if we end up leaning into a mono repo?

JASON: Okay, at first I was like, yeah, do whatever you want, but thinking about it that way in the keep related files related in an extractable way makes a ton of sense. If later we decide we're going to import this from, like, @company menu service, all we have to do is update this import. We don't have to reconfigure all these things. We're not extracting code from everywhere. Find everywhere something was imported and make it from the new package instead of from the local folder. I get it. I'm with you. Okay.

CORY: If we were to deploy that menu directory, we'd pull out that directory. We wouldn't be going fishing around all the entire various directories and whatnot to, like, then glue everything together. Okay. Menu view.tsx.

JASON: I know people like to capitalize their React components, I don't. I also get to do whatever I want.

CORY: Let's do an accompanying test file for it.

JASON: Right. Okay. Before I start. See, this is my habits, I immediately start writing code. Menu view.test.tsx still?

CORY: Yep. Cool. Yeah, let's create our component. I'm not going to make you write a class for this one. You can do a functional component.

JASON: Thank you. We're going to go export, const, menu view, and that's going to be a component, and component is going to currently return something like that, so we have a working React component in the most minimal way.

CORY: Cool, let's test it.

JASON: So, I want describe, I want it, and I want expect. Do we need any others this time?

CORY: I don't think we even need expect right now.

JASON: Cool, okay.

CORY: Describe menu view.

JASON: That's going to be one of these.

CORY: Edit away.

JASON: What does it do?

CORY: It lists menu items.

JASON: Okay. That's not going to be async, I don't think.

CORY: It is.

JASON: It is, because we have to use the service.

CORY: Yep, we have to call the asynchronous method, because one day it's going to go to Cheesecake Factory API and that's going to be okay. Let's render it. React testing library render.

JASON: React testing library, it's not doing it.

CORY: Testing library React. I forget

JASON: From testing library, was it there? There it is.

CORY: There we go.

JASON: Okay, it was render?

CORY: Yep, exactly.

JASON: All right. So, we've got render. Then we need to render our menu view.

CORY: Okay, perfect. And then do we want to see a pass real quick? You typed in menu view already. Yeah, just do like a screen.get by text.

JASON: What a wonderful thing that you just said that I don't know how to do. Is it just screen where does this come from, comes from here?

CORY: Yep, exactly.

JASON: Screen.

CORY: Get by text.

JASON: Get by where's my auto complete?

CORY: Where is your auto complete?

JASON: What don't you like? Testing library React has no exported member screen.

CORY: Is that right?

JASON: Am I doing it wrong, is it

CORY: I feel like VS Code is wrong.

JASON: No, it's the children who are wrong.

CORY: Yeah, it's definitely let's run it, let's see what happens.

JASON: So, someone's asking components in a subdirectory is a normal convention? It is, but the nesting that we have is we've got course, and then we've got components here, and then we're in a lib folder and a menu subfolder for, like, the menu related things we're doing, and then we've created another nested folder for components here. The reasoning being that at some point this menu, lib, may become an external lib. And all of the pieces live inside this folder, as opposed to me having, like, a menu component in the components folder, and then like a util, which was the fetching thing, then maybe I've got my tests in a test folder, and if I were to ever want to extract that, I've got to pull all of those out from their separate places, where in this case, I would literally be able to copy this folder and drop it into a new repo or a section of the mono repo where it's its own library. Thing for someone to argue against and now I'm fully in on. So, we're going to run this, and we're running this because I think it already failed. Not finding module testing library DOM.

CORY: Oh, really? That's weird. So, let's see... is it a

CORY: Let's install it.

JASON: Is something failing? Okay. We're going to NPM install. Testing library/dom. Oh, does this come out of DOM?

CORY: Okay, there we go.

JASON: Now it works because it's a dependency, like a whatever. I wonder if it's a peer dependency and that's why it didn't do the thing. Okay, now it's passing. We have a thing.

CORY: I don't think

JASON: Didn't pass anything in. I think we called it menu view.

CORY: Jason, that's the secret to get green test suite, just don't test anything, run it, everything's good. Okay.

JASON: I just do the .skip. Every test is skipped and you get 100% passing.

CORY: Okay, now we actually want to test our menu items, but these are coming in asynchronously, so we have to wait. Await screen.find by text is a promise. And it will wait for things to show up in the DOM. That one is not yeah, perfect, okay.

JASON: Find by text. There it is.

CORY: Yep. Let's just look for our first item that we put in that stub.

JASON: Okay, so, currently, it's still doing the thing, but we haven't actually put anything into it yet, so we need to refactor our component. You want it to break first?

CORY: Yeah, let's see it break first.

JASON: So, first item in the stub was cheesecake.

CORY: Yeah.

JASON: And now it's failing, or at least it should be failing or I wrote the test wrong. So, it's failing because we don't find cheesecake.

CORY: Okay, awesome. Let's implement it.

JASON: Okay. So, we are going to implement this by, let's see, I need to get my menu service let's see. We'll say menu and we're going to do a new menu service, but that needs a client. And, so, that client, if I can do the buttons how do I what buttons am I pushing? There we go. Const client is new menu item stub.

CORY: Yeah, I think we created it in our other test file.

JASON: Right, we can literally copy/paste it out instead of me doing this work manually. What a great idea.

CORY: Sorry, I thought we created the stub class in our test file.

JASON: We did not. No, no.

CORY: Okay, that was me being confused.

JASON: Okay, then I need to import this. Hello? Why isn't my auto whatever.

CORY: Maybe it's my directory structure.

JASON: From menu service. Then I should be able to get the service, and the stub. Then we have our menu service, so I can do const items equals await, menu service.get items. Then we're going to want to pass these in.

CORY: Let's do this. We're in Astro, right?

JASON: Yes.

CORY: Astro land is going to get serverside renders.

JASON: Yes.

CORY: For the sake of this exercise right now let's assume it's all client side. Let's pass our menu service in as a prop and have our menu service do the request.

JASON: Okay, so, we'll do a menu service equals menu service.

CORY: Yep, perfect.

JASON: Get rid of this. Then we need to go back into our menu view and add a component, which will be a menu service.

CORY: Yep.

JASON: And we need to figure out what that is. We'll type that in a second. But then we'll get our items. Oh, how do we do this? We're going to do items, set items equals use state, right.

CORY: Empty array maybe.

JASON: Then we'll use effect. Look at us go. Wait, is this is this there's a better way to do this now, right?

CORY: I think it's use in React 19.

JASON: Yeah, Dan is rolling over in his desk chair right now because of this.

CORY: I am not ready to do it. I'm too scared. Yeah, let's do this the way everyone is like this is awful, let's not do it, but let's do it.

JASON: Async function, load, and then we do const items equals no, it is items. We'll say res items so we don't have duplicate names. That's await menu service. Not going to get our auto complete because we haven't typed it out yet, but I know it's there, so I can do it like I did before TypeScript. Then we're going to set items to res items. And then down here... we're going to do ordered list? Do you have a specific way to do this so it plays well? Just do divs?

CORY: Yeah.

JASON: Okay, so, we'll do items.map item, and each of these is going to be a div, which needs a key. Do like item nope. Oh, my God. Name. And then we'll put in the name. And I guess we'll just destructure this for ease of application, I guess. So, then theoretically speaking now, this will all work once I get my imports fixed. What did I do to break my this is really disappointing. I seem to have broken my, like, auto import thing, which sucks.

CORY: Hate when that happens. It's so painful. I feel like back in the text mate days, this is how I would write code. Let me type out import the whole thing.

JASON: For the typing, can I just import type?

CORY: Yep, menu service.

JASON: Menu service. From then we're going to make this menu service, menu service.

CORY: Cool.

JASON: Now everything should be happy. Yelling at me for what? Oh, because I need to do this.

CORY: Yeah, that should be menu service. Yeah, menu services. Sorry, menu items.

JASON: Menu item, which means we also need to import this one.

CORY: I think that needs to be exported maybe, too.

JASON: Great. Great call. Export. Okay. Now we've got these. What are you mad about? Don't yell at me.

CORY: Seriously.

JASON: I appear to have like turned off my hover. Show definition preview hover did I do a thing that turns this because normally I can hit command I think I hit some forbidden keyboard shortcut.

CORY: Yesterday, I saw you messing around a bunch with Copilot. I'm just going to blame that.

JASON: Oh, no. I've done something silly, and it won't work anymore. Because this is like a VS Code thing that I've done, where I've like turned something off.

CORY: What's our test say?

JASON: What does our test say? What a great question. It is failing. And it's failing because it's not finding anything that says cheesecake. And it's not finding anything that says cheesecake, because why? So, this is unhappy, because it

CORY: Have we caught our load function?

JASON: Oh, I just didn't write code. That's the whole problem. What a dumb ass. Okay.

CORY: (Indiscernible).

JASON: Sure haven't, sure haven't.

CORY: Let's do that. Tests pass.

JASON: In the most painful possible way, we did the thing.

CORY: Okay.

JASON: So

CORY: Let's put it in the browser real quick.

JASON: Okay, let's put it in the browser, and the way we're going to put it in the browser is I'm going to open up my... index page.

CORY: Yep.

JASON: Going to get rid of this welcome thingy, and instead we're going to run menu item menu view, and we're going to do client only, I guess, on this one. And then maybe

CORY: That worked.

JASON: Kind of worked. Oh, you're mad about not having a menu service, which is a whole thing, because now we don't have how do we do this? How do we do this in Astro, because we can't put something from here in as a prop here for the client only.

CORY: Yep.

JASON: So, instead we would have to declare it on the

CORY: We could create a repper and do it or just do it in menu view.

JASON: Maybe we just do it in menu view, because I think we're going to make our lives pretty hard if we don't.

CORY: Yep, let's do that.

JASON: Okay, so, we're going to take this test out, or that part out. We're going to

CORY: That crop, too.

JASON: Here. Get rid of this prop. Can I just drop these in like this, or do I need to put them into the component?

CORY: Let's put them in the component just to be on the safe side.

JASON: Okay, then we've got our client, and this time now it works. I don't know what happened. That was so, at one point I'm going to get a new computer, because this one is haunted.

CORY: It seems that way.

JASON: I've just made some choices in the way this computer is configured over the years and I've installed, literally, everything into it, and that, as you might imagine, a problem. So, let's get it in the browser

CORY: Wait, did our test pass? Yeah, okay, we're in good shape. Yeah, put it in the browser.

JASON: Tests are fine. Now I go here and I save and cheesecake!

CORY: There are cheesecakes, oh, yeah.

JASON: All right, so now we have 30 minutes to get all the items up here and hit let's see, we're about 50% of the way to our first goal.

CORY: I think so, this is going to shock you. I think we're 90% of the way there.

JASON: All right, okay, I'm ready.

CORY: All right, let's go to our menu client interface.

JASON: Menu client interface, which is down here. Here.

CORY: Let's create our Cheesecake Factory menu client.

JASON: And the way that I do that is I export class Cheesecake Factory menu client. Implement menu client. And then we need the async get items.

CORY: I'm so curious, does VS Code support this? Will you humor me for like two seconds?

JASON: I'll do whatever you want. You're in charge.

CORY: Let's get rid of line 14. Now Cheesecake Factory menu client is red and squiggly. If you right click that, does it give you an implement members option?

JASON: Implement members I think the way it would work I tell Claude to do it, right. Now it's done.

CORY: Oh, perfect.

JASON: When the Copilot stuff is doing stuff that's easily inferred, it's fabulous.

CORY: Yeah, yeah, which is like another I don't mean to pat myself on the back, but I'm going to do it. When you're like coding to contracts like this and have a really good separation of concerns, bringing in things like Copilot becomes way easier, because it can infer those types. And all the changes stay isolated. Like, it's not like going into our views or, like, going into some Astro components and doing stuff. This is just implementing this thing and we'll drop it into our menu service and be off to the races.

JASON: Yeah. Okay. So, then

CORY: Fetch our menu items.

JASON: You want to do it in here, so I'm going to get my code back from here.

CORY: Yep.

JASON: And this time we'll do a fetch, and we're going to fetch from Cheesecake Factory. So, literally, just drop that in. That's fine. Do I need to do any, like no headers or anything. Just straight get?

CORY: Just straight get.

JASON: Okay, we'll say if, just basic checking. If it's not okay, then we're going to throw an error. And then we'll const items or I guess is it just items?

CORY: Oh, no, it's

JASON: Full menu?

CORY: In the browser real quick. I think it's categories and inside of categories it's products. Yeah, it's like categories and that's like appetizers. Then inside of categories you have products.

JASON: Images. Products. Okay. So, it's categories. Then do we need to map over the categories and then map over the products to like pull these out?

CORY: Yeah, we'll have to do like a double looper.

JASON: Double looping. Here we go. So, then what we're going to get is we're going to get the items, and those are going to come out of menu.categories.map. That's going to give us a category. Then we need to, from here we're going to then return oh, you know what we should do? We should just it flat map this, because it's going to make life easier.

CORY: You know what we should do that's going to make life even easier, I think? Let's create a variable called results, make it an array.

JASON: Oh, you're about to tell me to just write for loops.

CORY: Yeah, just push on to it.

JASON: You know, you can just make stuff easy like this, but then how clever do we get to be, Cory?

CORY: We can be very clever with our tests. I think, Jason, I know we're joking, but when I say software engineering is a team sport, I think this is like the sport that we're doing, of like you're writing something, I think I have a better idea, let me pitch it to you. You're either going to be like this is so dumb, why would you put a components directory in your menu directory, or I'll be able to convince you and we'll end up with a much better, cleaner implementation for the next engineer who comes down the road to work in.

JASON: Okay, so for this one we're just

CORY: This is going to be no, let's do a menu item.

JASON: We're doing it? We're doing it. This is going to be a menu item.

CORY: Yep. sorry.

JASON: Array of menu items. Nope, not like that it's not. Array. Menu item. Have the split keyboard and I pretend to know how to use it. Clearly, I don't.

CORY: Like I keep saying menu items, but you're so right, it's menu item.

JASON: Okay, we've got our menu item. So, we don't know right now let me how much of this do we need?

CORY: Just need name.

JASON: Just need the name.

CORY: For now.

JASON: So, for now what we're going to do is we are going to drop this down let's see... availability, images so, it is name. Okay. So, then we're going to, instead of this, we're going to push

CORY: A new menu item.

JASON: A new menu item. Like, of course, it would be a new menu item, because we wrote that class. It's going to be product.name.

CORY: Yes, sir.

JASON: Okay. So, what we don't have is our typing for like the menu, but we can probably fake that a little bit.

CORY: Yeah, let's fake it.

JASON: Do category is going to be an array of of products. And that's going to have an array in it of more stuff. And we'll just give it a name, which is going to be a string. Then we do one of these. Then we do one of these.

CORY: Yep.

JASON: Then we've got a type. And it's mad at me because why? Does not exist oh, oh, oh because because I'm looping over the wrong thing. It's the menu that we're looping over. So, menu and you're mad why?

CORY: Products

JASON: Does not exist on type string. Menu.category. It is an array.

CORY: Product. Has an array of projects with products on it.

JASON: What have I done? Menu.category. Category. Products.

CORY: Yep.

JASON: All right, chat, what am I doing wrong?

CORY: What is going on?

JASON: Why are you mad at me? Leave me alone. Cat. what do you think cat is? Must be type any. Object type. Bro, you are an object type. What? Category, array, thingy.

CORY: Yep, yep, that looks so right to me.

JASON: Am I using the plurals or the singulars? Oh, this is a good question. So, products, right. Categories. Oh, categories. Although that should not affect this at all, because this should not be a string. Why would you be a string? Is it for of? It is, I'm an idiot. Ha ha!

CORY: Cool, products we're in.

JASON: As usual, TypeScript was right, I don't know how code works. So, then we're going to return results and our tests should

CORY: Tests are still going to pass.

JASON: Because we wait, wait, is that happening on the front page yet?

CORY: We haven't hooked anything up yet.

JASON: Haven't hooked anything up yet. Haven't made any changes.

CORY: Seeing is believing. Let's go to our Astro component. No, our menu view, you're right. Now let's drop in that new

JASON: Instead of menu client sub, we're going to use cheesecake wish this would auto complete for me, there it is. Then we get it, it's unhappy. And it's unhappy because Cheesecake Factory, because I screwed up the name.

CORY: Cheesecake Cheesecake Factory. Yep.

JASON: And now those should work. I don't know what this is. So, we're going to get rid of it. And, theoretically speaking, we should see a

CORY: There we go.

JASON: So many. Oh, my God, you're going to be there for years.

CORY: It's a year long endeavor, for sure.

JASON: I love it. I love it.

CORY: Guess what, we broke all our tests. Or we broke a test.

JASON: Yeah, okay. All right. So, we broke that. Although, really, that should definitely be in there somewhere.

CORY: Should be in there. You know, with our tests, we don't want to hit an outside service.

JASON: No.

CORY: Like earlier in the stream you were saying like what if they get rid of a menu item or change a name of a menu item, which I feel like could definitely happen at Cheesecake Factory. So, we need a way to say for our tests let's use the stub.

JASON: Good. Okay, all right, so, the stub had a purpose. The stub wasn't see, it's all coming together.

CORY: Also it's also great to just have of like, I don't know, I was on an airplane flying back from Los Gatos last week and had the worst Internet possible and couldn't even though I paid the $8 for Internet, I couldn't make any network requests.

JASON: Right.

CORY: So, I could just load up all my fake and stub implementations and work on the application that I work on as if it had real data.

JASON: Right. And, so, now we can is there a mock? How do you mock?

CORY: Let's do Veet okay. I do mocking the easiest way that I have found.

JASON: Okay.

CORY: And this is because when I try to use, like, the mock module stuff, I always run into hoisting things and, I don't know, just feels like a wonky implementation. Sorry if anyone who works on vitest is listening and I'm bashing on it, it's great, I love it, but feels wonky to me. So, I use Spyon. It's under the viobject.

JASON: Sorry, which one?

CORY: Sorry, let's import vi, v, and now we get a spy on object.

JASON: Okay, so, I'm going to vi.spyon.

CORY: Now let's take this is where we start to refactor and make things cleaner. Let's take the thing that you wrote in the menu view that instantiates our menu service and drop it back into our menu service and give it a function. Wrap it in a function and call it get menu service or something like that.

JASON: Okay, going back into the menu service, creating a function, and the function is going to live in like in our class or separate outside our class?

CORY: Yeah, outside.

JASON: So, call it get menu service. And then we need to export that. In fact, we don't really need to instantiate that. We can just return it. So, returning our new menu service. And then

CORY: Now in our test yep. Call get menu. Menu service.

JASON: So, it will be service equals get menu service. And then we can simplify that on down, and then we don't need these anymore. Right? Okay. So, we've got our menu service in menu view.

CORY: So, now in our test file, we can import you have to import the entire menu service module. So, it's like import star as menu service module.

JASON: Okay, so, what this does is because we don't have a default export, we're getting every export out of this and then turning it into like an object.

CORY: Yep, and this is a very spy on API specific thing if I were doing this. Second argument to that is a string of the functioning. So, now we can say get menu service, and then you can say .mock return on the end of that.

JASON: Like this?

CORY: Sorry, on the end of the parentheses.

JASON: Right. Mock return. Mock return value?

CORY: Yep, that's the one. And now we can give it any object we want.

JASON: So, this is going to be a menu service. So, I do need to what the hell? Oh, menu service module.menu service, because we already have it. And then it's going to be a new menu what was it oh, right, menu service module.menu client stub.

CORY: That should work.

JASON: I'm missing a parentheses. Okay. So, now what we're saying is watch this module and replace get menu service to return this instead. So, now when menu view runs, it's going to use this instead of what we defined inside of the module or inside of the component.

CORY: Exactly.

JASON: Okay. So, theoretically speaking then, this means that this test is running without the network at all. It's running with our mock data.

CORY: Yep, exactly.

JASON: Which is one of the things that always drives me nuts about testing, is I don't want my tests to fail because of a flakey network or because of a third party provider. I don't need to test third party code. They should be doing that, right. And if I start testing third party code, now I'm always a step behind, like anybody who's tried to maintain something that's like yeah, we've all been in that world, and not doing that is ideal. Let's see. So, test is passing.

CORY: Test is passing. All fake data.

JASON: Still getting our stuff out here on the main thing. So, thing one is done.

CORY: Thing one is done.

JASON: Okay, back to the to do.

CORY: We can go in two directions. We could really tighten our belt and write an end to end test for thing one, or with the 15 minutes we have left, we can move on to start thing two.

JASON: So, I mean, checking a box is I feel like this starts to get into UI stuff, so there's two things that are interesting. The end to end testing is interesting, but to really talk about it we need more than 15 minutes. So, maybe what we need to do is just follow up with like a part two.

CORY: I would love to.

JASON: I think that might be worth doing, because there's a lot we're learning in here, I think there's a lot of really interesting, interesting stuff we haven't been able to touch on yet. So, why don't we try to get like one more thing done, and then we'll just come through and finish this on a follow up.

CORY: Cool. Awesome. Let's take a second to refactor real quick. There's one thing driving me crazy.

JASON: Okay.

CORY: Let's go back to our React component, our menu view.

JASON: Okay.

CORY: And I think let's let's take that use effect stuff out and put it into its own custom hook.

JASON: Okay.

CORY: Yep. Like for the sake of brevity, let's just create a hook in this file. Love it.

JASON: Like so. Then down here we're going to say const items equals use items. Let's refactor that. Use menu items.

CORY: Cool.

JASON: Okay, so, we're using our menu items.

CORY: What's our test suite look like? Green.

JASON: Who cares?

CORY: Right.

JASON: Because we're not testing implementation details.

CORY: Thank you. Put the words in my mouth.

JASON: I love it. I love it. And this is the kind of stuff I would always get myself in trouble on, so I love that this just kind of did the thing, and, like, as long as we didn't change behavior, we don't have to change tests.

CORY: Exactly. You will start to see in implementation specific scenarios, they will require implementation specific tests. Like like saying, oh, we're in React world right now. Right now, our menu view and our use menu items, those are React world. That is going to require a very React specific test. Meaning we're going to have to use testing library slash React and adhere to those APIs. Our menu service, those are so framework agnostic. It's just language.

JASON: Right, right.

CORY: So, those get to live just as like TypeScript stuff, whatever testing library you do. And you can like another stretch goal I have for this would be to like take all our components and just put it into Astro. And then we rely on our end to end test suite. And you'll notice that like sometimes you will have to change test code depending on where you are in your

JASON: Right. Because right now we're client side, and if we refactor to be server side, we could maybe find a way to keep some of the same test code, but, honestly, if we're changing where the thing works, we also changed the expectation. Because the other thing I would expect is now we're not awaiting. This should be true before the JavaScript is done. We don't want to await. We want that to be true immediately, so it's new behavior.

CORY: Yeah, exactly. Yeah. Everything except for our menu service is completely agnostic of where we're running it. It can run on client, on server, on some VM or container.

JASON: Right.

CORY: Don't matter.

JASON: Very cool stuff. So, we've got like five minutes of working time left.

CORY: Let's do the service and let's, like, pretend we're marking an item as eaten or consumed or whatever.

JASON: Okay, okay.

CORY: Let's clear house. By clear house, I just mean close the tabs. And let's open up our two menu service and menu service test.

JASON: Menu service test. And okay. Here's our test, here's our service. I'm going to bump these down. Can everybody still read that? Let me know if you can't and I'll make it a little bigger. That way we can kind of see what's going on.

CORY: Okay. Let's write our tests.

JASON: Okay. It marks an item as eaten.

CORY: Perfect.

JASON: Right?

CORY: Yep.

JASON: That's probably async.

CORY: Yeah.

JASON: Because we have to wait for user input. Robot input, I guess, in this case.

CORY: Also, this this to mark something as eaten is kind of oh, my gosh, what are the words I'm trying to look for? It's going to require some type of persistence, and we know that, because we're really smart engineers. Sometimes.

JASON: Sure. Yeah, that's me.

CORY: Every now and then we're smart, but we know like, it is implied that marking something as, like, done or eaten or completed is going to require a persistence layer.

JASON: Yes.

CORY: That's usually going to require a database or some third party service or whatever have you. Your assumption this having to be async is spot on, because it's going to require some network request at some point. Okay. So, I kind of hinted that we're going to need a persistence layer to store this information.

JASON: So, we got SQLite suggested in the chat.

CORY: I am not even going to go there yet, because this is our test. We were saying that all input and output should be hidden behind an interface. So, let's create an interface for our API that we are going to store retrieve eaten items out of. I wish I could say it

JASON: Okay, is this in menu service?

CORY: Yeah, why not?

JASON: Okay. And that interface

CORY: Let's call this a store. Like an eaten store, eaten store.

JASON: Eaten items store, that way we know exactly what we're doing, and it's going to just be like items.

CORY: Yeah, or maybe mark items as eaten.

JASON: Actually, this is a great question. How are we doing this? Because what we have is a name. So, the way that I would have done it by default is I would have had an array of IDs, because we're not loading them in. I guess, yeah, menu ID and then that I guess we could throw in the names, but this is a very interesting problem. How would you solve this?

CORY: I would solve this by relying on my buddy. And I think the fact that we're having this conversation is like why this exercise is great of being like let's write tests together. Let's write implementations together. When we get to a point like this, how does this work, we can feed off of one another. I would argue when you've eaten a menu item, no matter where it is on menu, it could show up in specials, it could show up in appetizers, you've eaten that item. I think a name is what restaurant can you think of that has one name to cover like multiple menu items? I would say I've never seen that before.

JASON: Fair, I'm with you.

CORY: Let's just hold on to a string of names.

JASON: An array?

CORY: Array of names sorry, let's do a set. Like once you eat

JASON: Right, then we can't get duplicates.

CORY: Exactly.

JASON: Okay.

CORY: I would make this another function. I would create this high level policy over my low level implementation. And I would say, like, this is called mark items as eaten or, like, has eaten, I don't know. I'm not good with words. This is an interface. So, this is just

JASON: What am I doing? No, you're right, you're right. This is like this, and that's going to return nothing, right?

CORY: It could. It could not.

JASON: I guess we could return our store, but we'd already have the store.

CORY: I would say as the engineer or as like the consumer of this dependency, what do we want a Boolean to say, hey, you successfully saved this thing or marked this as eaten, or would you want the entire collection back, would you want to fire and forget?

JASON: I would I mean, the way that I have done this historically is that I would expect that to then kick off a re render, so I would expect that changing the state is going to cause a re render, so I would do void, because I know it's going to get blown away and reloaded anyways.

CORY: Okay. I would say let's at least do a Boolean to say, like, hey, this was successful or this wasn't successful.

JASON: Sure, okay.

CORY: Let's make this async, too, because at the end of the day, we're probably going to be writing to a database, even though I don't want to make that decision up front, but who knows.

JASON: Yeah, no, not a terrible idea. Okay, we've got that.

CORY: Okay, then your items I would say let's also make it a method.

JASON: Make it a method?

CORY: Yeah, like get items, get the

JASON: Oh, get eaten items.

CORY: Yep. I feel when I say the word eaten, I'm fighting.

JASON: Do we want that to be a private property that gets returned by the thing?

CORY: A private property that gets returned by a thing

JASON: This would be the items.

CORY: That's the private property.

JASON: Then this returns that property. Right?

CORY: Yeah, yeah. Where it's like we don't know where what items what the mechanism to support items is, but we know we have a public I keep saying high level policy, of being like our public facing API. How are the things that are depending on the service going to use it. Items could be local storage. Items could be database rows. Items could be the file system, I don't know. But I know in order to access it I'm going to call get eaten items.

JASON: Right. Yeah, okay, I'm with you. Yeah. Okay. Yeah.

CORY: Cool. Okay. Let's do the same thing we did before, where we inject this into our menu service.

JASON: Inject it into our menu service. And the way that we did that was... what did we do?

CORY: Basically, exact same thing as menu client.

JASON: Okay. So, we have eaten is going to be eaten items store. Then we'll call it eaten is going to be eaten items store. And

CORY: Perfect.

JASON: Got that

CORY: Okay. Let's hold off on the implementation. Let's write the test.

JASON: Okay.

CORY: We actually broke our test above, because we broke now the contract of menu service.

JASON: Got it. So then this one needs to be

CORY: I would just do an empty object. This is where you get into like the category of test doubles. And I would just do like

JASON: This is where we're going to be in trouble, because we marked it as

CORY: Yeah, let's do as eaten items store. Empty object as

JASON: We're not exporting that. And we can update the import, and now it's going to not complain.

CORY: In the testing world, that's considered dummy, of being like we know we need to pass something in, but we know it's not going to get used to validate this test.

JASON: Got it, okay.

CORY: Passing something stupid.

JASON: Okay.

CORY: Let's mark an item as eaten, do a new menu service.

JASON: All right. And, so, this time we're actually going to need that eaten items store.

CORY: Now we're going to have to create a fake one.

JASON: A fake one?

CORY: Yeah. I would call it an eaten items fake.

JASON: Fake?

CORY: I can explain the difference between a stub and a fake.

JASON: Okay, so, then we need to actually

CORY: Exactly.

JASON: Export class, eaten items fake, and that's going to have

CORY: Let's do implement eaten items store real quick.

JASON: Store. Then let's have this

CORY: Can I bug you one more time before you do this? Let's get rid of items in the interface.

JASON: Here?

CORY: Let's say we don't need items.

JASON: Okay.

CORY: Awesome. Good enough.

JASON: I mean, good enough to test, right?

CORY: Yeah. I would say in this implementation, this is where we would create items, because we know the type of items of being like we know it's in memory collection.

JASON: We know it's in memory collection, yeah, okay.

CORY: Okay. We'll implement that later. Okay. So, now let's do menu service mark item or eaten item or something. I don't know, whatever we want to call it.

JASON: We said mark item as eaten. Oh, we haven't implemented this.

CORY: Nope.

JASON: And we'll say cheesecake.

CORY: Let's async it, because it's promised. And now let's get our eaten items and test the fact that our cheesecake is in there.

JASON: Get our eaten items. Test it's in there.

CORY: When I said async, I meant await, sorry, set you up for that one.

JASON: Yeah, then we'd say expect menu service eaten to include or contain? Contain cheesecake.

CORY: Yep.

JASON: So, this is failing because we don't have that implemented. And the way we're going to implement that is somewhere in our menu service...

CORY: Yep, just create it.

JASON: Menu service, we have mark item as eaten. That's going to take in name is better. Name. Then it's going to say this.eaten.mark item as eaten and we're going to pass in a name. Which we up there. Eaten items fake, mark items as eaten. Name string.

CORY: This is where I would create an items set in the class.

JASON: You would create it in here?

CORY: Yep, as like a class property. That's private. Only internal to this implementation. Yep, that should do it.

JASON: Okay, we are over time.

CORY: I know. Give me two more minutes. Okay, equals sign instead of the colon.

JASON: You're right, you're right. This is going to be this.items instead. This is going to be an equals sign. My God, where's the equals button?

CORY: Pressure's on. Okay. Then wait, we need to add it to mark item as eaten needs to add it to the

JASON: Okay, we're going to do this.items.add name and return true. So, down here we have why are you mad?

CORY: Oh, passing the fake eaten item. My gosh, I can't say the word eaten. Eaten. Just pass as fake.

JASON: Literally pass in the fake?

CORY: Yeah, create new eaten item fake. Cool.

JASON: Okay. Mark item as eaten, mark item as eaten. And now it's still failing. What's failing? It's saying should include cheesecake?

CORY: We haven't implemented the eaten. I would say on line 24 of the test

JASON: Line 24 of the test.

CORY: Call get eaten items.

JASON: Like that?

CORY: Yep.

JASON: Okay, so then we need that's going to be one of those, going to return one of those yeah, this get eaten items.

CORY: .eaten. Is that right?

JASON: No, no, eaten.get eaten items.

CORY: Exactly. Okay. Then theoretically speaking it's passing. We did our tests, everybody! All right!

CORY: Sweating a little.

JASON: Yeah, let's call it here, because we're over time. But this was great. And we're going to do a part two where we get into some of the interaction stuff, because I would really like to test how do we check on clicking things, because once we get into behavior and stuff, I think testing that well is tricky. So, making sure that we can get that figured out would be great. With that, let me jump back over here and I'm going to do another shout out to our captioner, thank you, Ashly, for staying a few minutes late today, that's provided by White Coat Captioning, which is made possible through the support of our sponsor Tuple. We also have a lot of good stuff coming up on the show. We are going to do what's on the schedule? I don't know, you're going to have to go look at the schedule, because we don't have time to show it to you. But let me throw oh, boy, something is going on with my mouse right now. Hello? Let me throw that in here. You've been learning from Cory today. You can find Cory on LinkedIn, and what else? What else is new? That's all. Go look at the Cheesecake Factory menu and cringe in fear.

CORY: Work through it.

JASON: Yeah. Send Cory the one that's going to hurt the most.

CORY: For sure.

JASON: Thank you so much. Any parting words for anybody as we wrap this one up?

CORY: No. This was so much fun. Thank you for having me. I love watching this show, so

JASON: Yeah, I appreciate you coming on. This was really, really fun to work through, and I'm excited to come back and get even further into this. I hope you all learned something, I know I did. Cory, thank you so much. We will see you all next time.

CORY: Part two, love it. Thank you.

Learn With Jason is made possible by our sponsors: