Testing the Home Component I
In this lesson, we begin preparing tests for the Home component.
Get the project source code below, and follow along with the lesson material.
Download Project Source CodeTo set up the project on your local machine, please follow the directions provided in the README.md
file. If you run into any issues with running the project source code, then feel free to reach out to the author in the course's Discord channel.
This lesson preview is part of the TinyHouse: A Fullstack React Masterclass with TypeScript and GraphQL - Part Two course and can be unlocked immediately with a single-time purchase. Already have access to this course? Log in here.
Get unlimited access to TinyHouse: A Fullstack React Masterclass with TypeScript and GraphQL - Part Two with a single-time purchase.

[00:00 - 01:00] In the last lesson, we quickly set up this dummy test to ensure that our just sweet runner was working appropriately. And we also installed the two separate libraries that we would need, the React testing library, as well as the Apollo React testing utility. In this particular lesson, we'll actually begin writing some tests for the home components. Now, if we recall what this home component is, it's essentially the landing page of our app. It's when the user visits the index page of the application. This is where they're able to be see a search input where they can search for certain cities or places they want to stay at. There's a few presentable cards we surface for them to click and navigate to a certain city. This is the section premium listings in which we actually make a request in this particular component to get the highest priced listings in our application. And we just simply showed them here, the highest for price listings. Lastly, there are two other cards in the bottom, just that act as links to certain cities that they can click and navigate to.
[01:01 - 02:39] If we launch the actual home component file as well, pretty much what we just quickly mentioned when looking through the UI of this home page is represented here in the code, you know, when this component is first rendered, this is where we make a query. This is the query that we try to get the premium listings off. This is where we specify a page limit or a limit of just four listings. And where we specify the filter of finding the listings that have the highest price to the lowest. The on search functionality that we've set up here is purely for the actual search input at the top of the page. This is where we try to determine that the user is actually searching for something that they haven't just typed empty spaces. If they did, we'll just display an error message. However, if they actually search for something valid, we try to navigate them to the listings route and specify what they've searched as the sub routes. And lastly, the only other big piece of this particular home component is how we render the UI based on the query. The majority of the UI in our page, like the hero section, the links to the four different areas at the very top, the two links at the bottom are static and they remain there regardless. But in the premium listing section , just in that section alone, right here, this is where we conditionally render different UI based on the status of the query. If the query is loading, we render another component called home listing skeleton that just represents a skeleton loading state. Once data is present, this is where we actually render the home listings. And finally, if for some reason or another data doesn't exist and loading is not true, it's probably means that the query somehow didn't work. We don't worry about displaying an error.
[02:40 - 02:55] In this case, we just return null, we just said don't show anything. Now, before we even begin writing tests, let's quickly talk about what we want to test in this particular component. It's a few things I tend to follow or a few patterns I try to stick with.
[02:56 - 03:32] The very first thing is I tend to avoid testing static content. So we mentioned in this particular page that there's a little bit of information that's going to be shown regardless of a status of our premium listings query, right? So things like these four city cards, things in the bottom, such as these two cards are going to be shown all the time. If our query here fails, if it's successful, it doesn't matter. The fact that we're saying this is Toronto, Dubai, LA and London isn't core to our app, right? Like these four cities don't have to be shown. Maybe down the line in the future, I might change these four cities to maybe just show to.
[03:33 - 04:00] Maybe I'll just pick different cities. As a result, I don't necessarily think this is suitable tests to write, right? They're purely static. They're not dependent on any other dynamic functionality. They're simply links that take the user elsewhere. So as a result, I'm not going to test for the fact that we're rendering cards here. The second thing that sort of might tie into the first, depending on the case is I tend to not test things that are often handled by another library or tool.
[04:01 - 04:37] So for example, we know that each of these cards are links to their respective listings page. So for example, if I click this, I'm taken to listings slash Toronto. If I click this, I'm taken to listings slash Dubai. How does that work? We're using the React Router link components, right? And that was part of the home hero section where we actually use React Router link, or we have anchor tags regardless. If, for example, we're using the React Router link components, we know that React Router is taking care of it for us, right? The React Router library is probably tested to a detailed extent, right? They've tested how their link component works.
[04:38 - 05:06] They've tested how all the different variations are set up. As a result, I'm not going to write additional tests to verify that if this is being clicked, it takes me to Toronto, right? I think it's fair enough to assume as long as I've used the React Router library appropriately, it's going to work. That being said, what do we test here? Well, there's one section in the home page that's somewhat custom dynamic functionality we've created. And that's this search input, right?
[05:07 - 06:07] If I was to just press space, space, space and click the Enter key or press this button, we see an error message. However, if I actually type something, let's say LA and I click Search, it takes me to LA. This piece of dynamic functionality is fairly important in how we want this input to work. So I think that satisfies to be suitable tests we can write. Another piece of dynamic custom functionality we've set up just for this page is how these premium listings are shown. When it's the request to get these listings are successful, we show these cards . If this request is unsuccessful, we don't show anything. If this request is still in the loading status, we show a loading skeleton or a loading UI. That's pretty relevant to what we 're trying to do here, right? And writing tests to support that will give us the confidence that the way we've set up our components is the way we want it to be done. So with that being said, let's go back to our code and sort of structure the way we've just talked about in terms of the test we want to write.
[06:08 - 07:01] We'll get rid of this dummy test we created. And we've mentioned how we're going to set up just two subsets of tests, right, for two different pieces of concerns. One is the search input and one is the actual premium listings we want to show. So this isn't something that one has to do, but I'll look into breaking down where we group these tests. So I'll have a describe block to represent just the search inputs. And I'll have another describe block to represent the actual premium listings we want to show. I'll comment this out first, and we'll begin by thinking about what are the tests we want to write for the search inputs. The very first test we can think of writing is a test attributing to having the search input be empty or blank upon the components initial render.
[07:02 - 07:30] So we can say something like it renders an empty search input on initial render . Before we even begin thinking about writing our assertions and whatnot, let's try to simply render the home component in our test. To help us do this, we'll import the render function from the react testing library or testing library reacts.
[07:31 - 07:50] Right, and when it comes to rendering the home component, all we need to essentially do is use the render function and specify the home the components we want to render. Since we're actually rendering a react component, this is where we'll also import the react utility from the react library.
[07:51 - 08:20] If we were to save our changes now, head to our terminal and try to run our tests, we can wait and see what would actually happen in this instance. The test seems to fail if I go to the top of the logs, it tells me could not find client in the context or past in as an option, and is referring to the use query hook using the components.
[08:21 - 08:50] We've talked about this in one of the lessons previously, and we've mentioned that our test is unaware of what we're doing with our queries. So we'll essentially now need to mock the query behavior. Note that even though this test isn't necessarily relevant to us making the query, for us to simply render the components, do we actually need to have the query functionality? In other words, the Apollo provider in our actual client application be mocked.
[08:51 - 09:40] So with that said, we'll also now import the mocked provider from Apollo react testing. And now with the mocked provider available, we'll wrap our home component that we want to render in our test with the mocked provider. In this instance, if you recall, we will need to specify a mock property. And this is where we essentially mock the graph QL risk requests we want in our components. In our home component, the search functionality is irrespective to what our query does. So this instance, even if our query is in the loading error states or when data is available, the search input will always be there. So in this case, I'll just simply use the empty array value here.
[09:41 - 10:09] This would mock that the query is still loading, but it wouldn't affect what we're trying to do in this test. A quick tangential notes, the package JSON file, I'll say try to make sure your react or Apollo specific libraries are up to date. I just saw a few separate issues related to the fact that mocked provider was unable to actually mock the actual Apollo provider. I 'm not sure when you'll be actually viewing this lesson, this may be very far into the future.
[10:10 - 12:47] But to help alleviate some of the concerns, I was able to resolve some of those weird bugs I saw by making sure my react hooks a polar package is in the same, um, it's called version as my polar react testing package, 3.13. But regardless, if you're seeing an issue here and you've done exactly what I've done here, just Google around and you'll see some other people might have noticing similar issues to the fact that for some reason, the mocked provider wasn't actually mocking the Apollo provider. Quick tangential note, but if it works, awesome. Now, if we actually go to our terminal, we'll see another error. It's pretty convoluted, but it tells us something's wrong with the link and router components in our test. If we head over to the react router documentation, it actually tells us, and we may notice already, the react router relies on react context to work, right? And if we notice a recall in our source index file of our client, we sort of wrap our entire, all our components in our client on the router, then we specify links and routes within by being able to be contained within that router context. And it tells us if you try to unit test whatever your components that renders a link or route, you get some errors or warnings about context. While you may be tempted to stub out the router on your own, they recommend to wrap your unit tests in one of the router components. The react router, sorry, the react testing library documentation gives a simple straightforward example that we're going to follow . And it's simply being able to import the router components from react router DOM, and then specify a history value in the history prop that it expects. This history property will then have information about the full history or full app rendering and navigating. It also gives us the capability to sort of push routes to test our components under certain conditions. So for example, in this case here, they're basically saying you want to test the behavior in a 404 page to do so, you can just simply use the create memory history function from the history package, which is available globally. Push a specific route that you want, specify that particular history in your router components in your tests. And then you can assert the functionality that you're looking for in under the specific route that you want to be in. So with that being said, let's actually try this out. We'll import the router components from react router DOM in our tests .
[12:48 - 13:12] We'll import this create memory history function from the history package. And again, history should be available globally. So there wouldn't be a need to install it at all. We'll then create a history constants, which for now is just directly the function run itself.
[13:13 - 13:36] And then we'll specify it as the value of the history prop for the router. Now, if we were to look in our test suite, there's a few things happening. But the very top, we see that our test passes. Remember, we're not actually asserting anything right now. We're simply trying to have our components be rendered in our test. And by just seeing this pass here, it tells us we're doing it. Okay.
[13:37 - 15:36] However, there's still a few other console errors that are being generated. One of them is this saying that error not implemented window.scrawl to. Why is it saying that if we go back to the home components in one of the bonus lessons or the add on lessons we added, we specified how to create our own custom hook called use scroll to top that allows the user to navigate to the top of the page as they navigate from one client route to the next, not very pertinent to what we're trying to do in our test. We don't necessarily care about this. So what we can try to do is just simply mock or just have this console error go away. And one simple way of doing so is trying to manipulate or directly manipulate this scroll to properly property in this window object. So we'll just say, you know, it's an empty function that doesn't do anything. And at the very top, I'll just put a comment saying, just remove console error with window scroll to. Now, if you head back to the terminal, we'll see that our actual test passes and that window scroll error is now gone. However, we see another error show up. And I've just saved the file again to have this rerun, just to make sure it's being there. So yes, we see our test passes. We'll see another console warning. Is this a complete blocker to continuing? Not necessarily, but it does tell us something might be wrong. And it just says an update to home inside a test was not wrapped in act when testing code that causes react state updates should be wrapped into act. Act fire events that update state assert on the outputs. This ensures that you're testing their behavior, the user would see in the browser, and there's some additional documentation in the Facebook react docs. Now, this was a newer console warning that the react library started to introduce.
[15:37 - 17:22] And this is most likely attributed to the fact that in our tests, we're not being prepared for the fact that our component needs to wait for something, right? Even though the empty search input or the search input itself is irrespective to the status of our query, there are some state changes or some work happening in our components that depends on the status of this query. In one of the lecture presentations, we quickly talked about a utility that the react testing library provides called wait for. And this would help us sort of specifying our test that we want to wait for any asynchronous work or if any other changes are to be made. So what we can do is we can just simply use this particular wait for utility. It's to be an asynchronous utility or function. So technically, we'll say wait wait for its callback function. So take a value within since we 're specifying the wait keyword here. We'll say that our it's a block is to be asynchronous as well. Save our changes. Now, if you go back to our terminal, boom, our test passes and all those console warnings we just saw has now been removed. Now, everything we've done here so far was simply trying to get our actual component, the home components simply be rendered in our tests. Notice that we're not doing any assertions go so technically this test isn't ready or fully complete just yet. A pattern that I tried to follow similar to what I did here is when I'm first beginning trying to work with having components be tested, the very first thing that I try to do is just simply try to render the components in your tests. There's a lot happening here, as you can see, right?
[17:23 - 18:16] It's not that difficult, but you have to think about making sure we're able to specify a mock polar provider. If there's any issues with routing, we need to specify that we can use the route to component from React, router, DOM. If we saw console warnings with the scrolling capability that we did, we just want to remove them in our tests. So by doing all of this, we're now prepared to think about, what do we actually want to test for in this test? We essentially want to check the value of the search input being shown in the home page components. To be able to check for its value, we need to find that node in our tests. And this is where we can now employ one of the utilities from React testing library to find the node that we're looking for. What utility are we going to use? The home hero child component in home is the one that contains the search input .
[18:17 - 19:50] This particular search component is from and design, and it renders the HTML input that we're looking for. One utility that we can use, which is pretty apparent to the user, is the place holder of this input. We say the placeholder is search San Francisco. There exists the utility gets by placeholder text that allows us to find a node based on the placeholder text of that particular node, which is useful for inputs. With that said, we can now try to find the node, we'll say constant search inputs equals get by placeholder text, the placeholder of that particular input, which is search San Francisco. And now we can simply try to check for the value of the search input to specify that it's going to be equal to an empty string, which essentially is an empty input. So we can say expect, this is where we specify our assertion that the search inputs dot value is going to be equal to an empty string. Now, if you go to our terminal, we see that our test passes . We're still in the watchable. So technically, this shouldn't mean that it picked up the changes we just did to verify this. Let's try to provide some information here. So let's say to equal Hassan, my tests rerun now.
[19:51 - 21:00] And we see a failure, and it's able to tell us where it probably failed. So this is the actual rendered template that we don't see. So all of this logs here is essentially what ant design is rendering for this home component. But more importantly, what it tells me above is it expected Hassan. That's what we said it should be, but it received a blank string. So this is just a verification check that the test is actually picking up the changes we made. One last thing to do in this test is when we hover over this value property here, we notice a TypeScript warning. And it tells us property value does not exist on type HTML elements, where this HTML element comes from, it comes from the function get by placeholder text, where it returns a node of type HTML element. The value property is only relevant to HTML input elements as to why this function doesn't specify that as well, not 100% sure. But at least we know as a fact that we're trying to find an input. So this shouldn't be just an HTML element, we can typecast this to say HTML HTML input element.
[21:01 - 21:59] When we save our changes, we can see that this particular TypeScript warnings now gone. That's because the value property is a valid property of HTML input element. If you go back to our test to confirm our test is running correctly. And we've been able to run our very first relevant test for one of our components. Now a quick recap of what we just did. So a few things we did, right? But when it comes to our actual assertion, it's very straightforward. Everything else we did was just make sure that our component was to be able to be rendered in our tests. We use the mocked provider utility utility to essentially mock the Apollo provider being used in the component, even though our particular test is irrespective of what the query does. We're using the router component from react router DOM, because the home component uses link or route components. And as a result, we need to sort of mock the behavior of the router context available in the component.
[22:00 - 22:26] We're using the wait for utility here. And this is primarily for the fact that our home components has some work being done asynchronously. And just to help avoid that console warning we saw before, we're making our assertions happen only after we wait for potentially any changes that are to occur. And lastly, we made some specific things around making sure that scroll to warning is gone. And then we came up with our assertion, we just try to find the search input node.
[22:27 - 23:18] And we try to assert that the value is equal to what we expected to be. One thing that people tend to do after a while is you may notice that a lot of your tests might be setting up your rendering for your components to be very similar. So essentially, every test that we'll write in this particular components will set it up very similar to this. So one could simply create a function or utility function that takes an input of sorts, they just simply renders it as is. I tend to try to replicate as much as I can in each of my tests and help avoid using functions as much as I can. And there's no right or wrong here. But as we move forward, we'll see us replicating this over and over again. And as we move forward, now knowing what we know now, we'll start to move a little bit faster since everything that we'll do from here on out is either set up very similarly, and or our assertions would either be similar or slightly different. Awesome. I'll see you in the next lesson.