Enterprise UI Development Test Scope
Transcript from the "Test Scope" Lesson
>> I'm gonna use this as an excuse to kind of like, think about the architecture here a little bit. And what the first thing I want to do is separate this component from the state management library a little bit so that I can swap out any different provider and it'll just work with it and life will be great.
[00:00:19] And we could take this really far if we wanted to, we probably won't but there are a bunch of things you can do. And some of these are just kind of useful patterns, right? I'll revisit this at the end. But if you think about Redux, kind of like prior to Hooks, you had those higher order components where you would have just a view layer only version of the component.
[00:00:44] And then you would have the container component that would wrap it and pass in all the Redux stuff as a prop. We stopped doing that when Hooks became a thing, but it's actually a pretty good pattern that will make you happier and less sad over time. You can still do it with Hooks, we just don't because we're very lazy.
[00:01:02] We don't like typing, despite the fact that is what we do for a living. So we can do some version of that here and it will both get me to a place where my test will pass and then also leave me better off for using this component later on.
[00:01:16] And that seems like a double win. So the biggest problem we're gonna have here is in this little sample app is naming things is hard. And everything it's always hard. But in this case, what we wanna do is have the ability to have this component with wrapped and unwrapped from a provider.
[00:01:38] Now the nature of this component makes it so that I'm not gonna win super well here because cool, I can pull this out and I will pull this out. I will have a problem in about 20 seconds after that, which is if I go into the item list itself, or the item itself rather.
[00:01:59] Yeah, I don't know if I was going the right direction. And I go into the item it's using a useDispatch which assumes that we are and useItem is using use selector, right? And so you cannot have this component because Hooks are a thing. You cannot have this component totally separated out from any kind of provider whatsoever, but you still wanted this idea that you could snap it into totally different systems, right?
[00:02:30] And this comes from the fact that we chose to use Hooks. If we didn't choose to use Hooks, we probably wouldn't be here. And if we did it in unit test, we wouldn't even notice the issue. But here we are with an architecture that we need to solve for.
[00:02:43] So what I need to do is I need to wrap it in some provider, but like it would be nice if it weren't totally coupled all the time some closures that I don't have anything. Because this is like, even if you mounted a different one, it would still be hooking into the same state.
[00:03:01] There's a bunch of grossness here. Luckily, we can start to unwind this a little bit. So let's assume that we'll have, since this is the entire application, I'll just call it application it's fine. I don't love the name, but I don't have better. I can call it packing-list-container 2, I don't know.
[00:03:19] The naming is not the important part. And so let's return in this point, we'll grab, just really the provider of this point. This will be the one wrapped in the state management. I'm not gonna look at my test while I'm doing this because stuff is gonna break. So now I've got just a pure React component.
[00:03:55] And if I really wanted to I could use children here to take these lists out, which would then make us I could render this component now that it wouldn't show me any items. And I have to do a bunch of other weird stuff. But I could begin to further break apart this component and make it a little bit more modular with children, a bunch of other things like that.
[00:04:14] But I've started by at least, separating out the part wrapped in the provider from the part that's not. So then I could say like, application will be the thing with default. And then we'll also export the Packing List because this is why I said before, having a version wrapped in all the state management is great for when you use it.
[00:04:32] And then if you ever just wanted the raw pieces of UI and be able to pass props in, you would also have a version that you could export as well as both the default and the named export. I'm using export default because it's very common with React applications.
[00:04:46] I tend to in my own apps just never use export default and have named exports for everything, cool. So we've got that in place and I haven't solved the issue yet because we're still putting in the default one. And if I just try to grab the PackingList, we'll get a new error.
[00:05:01] Let's go view the new error because I think it's worthwhile. So let's go and we'll adjust our test a little bit. At this point, we're going to get the uniquely exported one, which blows up lots of my tests all of them for those keeping track at home, which is like, hey, you tried to use this batch but you don't have a provider.
[00:05:23] That is true. I do not have a provider. So we've got that, could we export the provider? We could, but you know what, I'm gonna recreate at that point, my original problem, right? Cuz then I will still have one with the store enclosure state. And I will just be back where I started with a slightly more modular component like better than when I started but with the same issue.
[00:05:49] So let's not deal with that just yet. So one of the cool things about React testing library is that you can, I could do something where I can even wrap it, like I can pull out a provider, I can wrap each one. Now again, like the store is my issue, not the provider.
[00:06:29] There's one store to rule them all, use it, it's in closure state you don't have a choice. What would be better? And could I grab configure store? I totally could. But then I'd have to grab the reducer, and I'd have to whip this together for every test and throughout my application.
[00:06:44] What I could do on the other hand is I could say. CreateStore, create application store, whatever makes you the happiest and this is a function now that we export and, Now I have the ability to get a fresh store and I have one that is available for use through my app, right?
[00:07:16] I have not lost anything. I have only gained, and I gained a lot for what is formatted to be four lines of code, right, and node a ton of characters. So we have that in this case, and that's super helpful. So now what we could do is we can pull that into our test.
[00:07:38] I feel like naming stuff was really hard. And then Intellisense made it harder because I don't want to use like really common reasonable names anymore because autocomplete is really nice. So we could go ahead and let's say for instance, we just we're gonna use this test right now as an only test because if it passes we know at least we have a path for it.
[00:07:59] I don't need to watch all seven fail all the time, or for different reasons. Cool, it fails for the same reason it failed before, there's not a provider. So we could say, Provider and that is gonna come from React Redux in this case. Provider, and then for the store, All right, now, we've got a passes.
[00:08:39] We've got a fresh store. I'm very lazy. I don't wanna do this seven times, because I didn't even write all the tests. There's also so I can edit one I can do all sorts of stuff. I'm very lazy. I don't wanna wrap everyone in a provider just yet.
[00:08:56] What I would probably do in this case is, I could do something like, Yeah, let's do this move, right, and I can create my own render. Let's do something else first. One thing that testing library gets super nice is I can paste this option, a set of options to it afterwards, where I do, I give it a wrapper.
[00:09:29] And this is great for error boundaries and stuff like that. I can give it a wrapper now. Every component that goes to that render will get wrapped in that other component. Now I want to do that in mass so I'm gonna create myself a little helper function like we saw earlier that's just going to say like, hey, we'll have a version of render that we'll call it renderWithProvider for now.
[00:09:56] And that will effectively be very similar to what we saw before. Let's just start very easily we'll just React element. This is like, remember when we did that render that also brought in the user? We're basically doing that again. We'll make this better in a moment. And so we've got that in place.
[00:10:19] And this one will hypothetically, Return render, and let's just grab this. Right, ro now we've got that or return all the same. It's basically API compliant already if you look at this.
>> How is this different than before each?
>> I could do it before, but then I have it, like that is literally rendering before each.
[00:10:51] I won't pass any other options. Like, if this took props, and I want this one to now take props, now I'm doing the same one before each no matter what. I don't have any real way to pass props in. In this case like it would work, right, but it doesn't work writ large.
[00:11:08] And so really all I could do is like as you saw me right before, and then swap this one here,
>> What is the little dash before render do again?
>> Just gives it a different name so I can call this render, right?
>> Just to give it a different name so I can call this one render and now if I look that one renders let's see This should just work at this point.
[00:11:44] Let's see what we got. I had a real issue and I had a dumb issue. Let's deal with the dumb issue first and get it out of the way and then we'll finish up with the real issue. The real issue was actually should be solved at this point, but the dumb issue got in the way.
[00:11:55] I made a big deal before about you can put emojis in a name and the role will strip out the emojis. That is still true. Not true for labels, right, which are just actually matching the test. So I have two options here. And honestly, they both go into, what you should probably have in your test is a little more flexibility.
[00:12:19] When your test break you want them to break for a really good reason not because someone put in an emoji in the UI. Because next time the designer goes on PTO I'm gonna put some emoji in the UI and the test shouldn't break or maybe they should, right, depends on like the spiritual version of that.
[00:12:34] So I can either use a regex or I could use the fact that emojis are stripped out and still be case sensitive. At this point, it's working with the case insensitive regex, I think that's totally fine. But yeah, it also shows that you want your test to break cuz your functionality doesn't work, not because of a tiny implementation detail like an emoji.
[00:12:58] This is what we are whipping up a brand new store. I did change it a little bit as I was playing around and separating stuff out slightly, which is I make the store as a variable. I have this ability to wrap it in a provider. Right, which is just the child at this point.
[00:13:15] The really cool thing you saw me typing earlier is that React testing library will, if you're using the same wrapper all the time, and you don't wanna, let's say, this is one provider. Let's say I had that thing where you start out with one context provider, especially if you're using the context API, and the next thing you know you have seven of them.
[00:13:35] Could you have put them in over, and over, and over again, sure. One of the things I would probably put in the utilities is either a wrapper, or something called all the providers, right? And then this wrapper option will then take your component and wrap it in that entire hierarchy so you don't ever really have to see that.
[00:13:51] But the kind of core piece to kind of summarize is My tests were failing because of a problem with my architecture. My problem with my architecture was that my state management and my UI were tightly bound to each other. And made it difficult to actually use this component, and that manifested itself in the tests.
[00:14:15] Like I said, this is a small example. Stuff like this has manifested itself in reality. But at that point is so like ingrained and because I don't have the test sometimes to catch these things early it's very very hard to refactor. And that just becomes like I guess this is us now.
[00:14:29] I guess this is how we live. And separating out the other thing and I'm not gonna do this all the way. Because we're already in the weeds a little bit and I don't want to be too React specific at this moment that I would think about doing just kind of like some food for thought for later, is like I said, it still has to be wrapped in some kind of provider because you can't take props.
[00:14:48] So things that I might choose to do later on kind of is my thinking around this which is cool, would I make these children, I might. A lot of times what I will do is like dependency injection is super powerful which is, if you look at the individual item for instance, right?
[00:15:09] Or even the item list, this like use item IDs is a use selector and it needs to be in a provider or else it will blow up. The trick in this case is like, what if the ability to fetch stuff from the state was something I passed in, right?
[00:15:24] If somebody passed in, now, this component can exist outside of a React component hierarchy without a provider. I can pass it in it will use that hook. And then I can also pass in other stuff when I need it. And the idea is like you're just trying to take your code base, and this is true in a large application.
[00:15:41] You're trying to take all the ways that it's like jammed together and start to pull it so you can pass stuff in and you can kind of pull it apart and snap it together rather than the fact that pieces are glued to each other. And just to be totally honest with y'all, preparing for this, I'm like, I should grab some of these different functions from my codebase and pull them out of the large codebase and drop them into examples.
[00:16:05] And I realized that I had some work to do on my own codebase, right? There were things where I couldn't just lift this data transformation function. Because it had a bunch of imports that were then kind of like tied to other files and next thing you know, it's like you go to grab what you think is one piece and stuck to it or like 14 other files, right?
[00:16:25] And that is a sign that maybe things are problematic, right? I think the thing that I learned kind of getting ready for this was that a great great litmus test for you might think that your code is modular and like, yeah, there's the thing that takes it from the API to how I want to use in the UI.
[00:16:44] It's like it just takes these two things but now over time, some of these were not my fault, some of them were. Or someone just imported some other file, right? Well, that's now in the closure scope, right? So you gotta bring that file too. And then that file imports another file.
[00:16:58] You got three files. And that one imports two more. You know what I mean? And you get this network effect where it becomes very difficult. And it is sometimes take what you think the most modular part of your code base is, and try to pull it out and drop it into another folder.
[00:17:14] And see how much stuff breaks, right? Is a great way to see, did you do this as well as you think you did? But these are the little things you can find from the testing, and they also point out some of those patterns and ways to structure your code, so you can pull stuff and move it.
[00:17:28] It becomes easier to refactor. You wanna move into a totally another directory? Cool, it just works. Move it out of the project. Great, you want to pull it into its own library, awesome. But also, it also makes your code more testable, right? We didn't do that in service to the test we did in service of having a more maintainable code base that was highlighted by one of our tests, and we fixed our tests along the way, awesome.