TypeScript/React Testing: Components, Hooks, Custom Hooks, Redux and Zustand

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
time for another viewer request video and this time we're going to take a look at typescript react testing so many of you wanted this one and i am happy to provide so on this blue collar coder we're going to take a look at the react testing library and not just that we're going to actually also test hooks and test connections to redux and zustand see i got that right it's really going to be fun stuff but in the meantime of course i want to tell you about my new book no bsts the book it's just out recently and it covers every single no bsts episode it's really exciting stuff and it's going to cover this video right here since i think it's an important typescript topic but okay i've prattled a lot enough let's go check out how to do react testing okay so i've set up a create react app using the typescript template and i brought in redux and a couple other things so that we just don't have to deal with the yarn stuff but i'll show you those as we go along it's the standard out of the box app that we've seen before let's go take a look at app.tsx it's got the spinney logo and all that but if you notice there's actually the app test tsx so this is in the same directory as the app.tsx file and it runs a test and it uses the react testing library it's actually pretty simple stuff you render the component that you want and then you go and kind of poke at it a little bit and you create some expectations around that so in this case it's going to look at the app component and it's going to find the learn react text and the element for that which is going to be a link and just expects it to be there it's not a particularly sophisticated test so the first test we're going to do is i'm going to go and create a person component that's going to render a div with a person's name in it and then we'll go and use this kind of testing to make sure that that component works so let's go and create a new file it's called person.tsx and we'll import react and then we'll export a cons person component it's going to take a name and it's going to output a div with name is and then the name and of course we need to put some types on that so one way to do that is simply just put it right here name is going to be a string okay so let's go and make a test for this i'm just going to go and copy and paste this because i'm lazy and i'll create a new file called person dot test.tsx and then i'll bring in person from that person file and we're going to see if it renders a name now if you're unfamiliar with jest which is the underlying unit test framework here you basically define a test by using the test function it takes a name and then it takes a function that's going to run that test so in this case we want to render the person we want to give it a name and then we want to go see if it's rendered properly so now we could just go and see if it has name is jack in there let's actually give that a try to make sure so we'll do yarn test and that's actually going to run a test watcher which is actually kind of cool so hit all to see all the tests running and it looks like you passed both that's really good so now looking at my text is just one way of finding an element you can also get by roll so this would be the aria role and let's just say that we want content info is the role that we're looking for so now we need to make a change over here to say that our div has that role content info come back over here and now we should have that div element and we can also test it differently so we can say that we expect that this div element has the text that we want to do that we just say to have text content and you just give it the text you want so in this case its name is jack there we go let's take a look at our test and we can see that we're passing again but let's see it not pass so we know we passed in jack let's pass in foo instead and see that in this case it doesn't pass and we can even get some interesting information here it says name is foo when it's looking for name is jack okay cool so let's fix this again and we can even test attributes so we can also add more expectations on ourselves and say that our div element is going to have an attribute and you just give it the attribute that you're looking at so in this case that'd be that role and we expect that that role is well let's start off with nothing see it fail yep there we go we expected that it was going to have nothing and it has content info so let's change that to content info and there you go we pass cool okay so what happens if you've got a component that has multiple elements in it that you want to test so to try that out i'm going to create a new component called sidebar it'd be a navigation component where you give it a bunch of items including the name and the href and it goes and renders them out and we then need to go and test that to make sure it does the right thing i'm going to go create a sidebar.tsx file and i'll bring in person just to get a good starting point on that and call this sidebar and it's going to have items and that items is going to be an array of objects where you've got a name which is a string and then a href which is also a string and we're going to have an array of those okay so we don't need that content info there and we're just going to go iterate through those items and that's going to give us an item and then with each one of those we're going to have a div and within that the anchor tag with the name looks good and we'll use that href as the key probably should be unique right and we'll just put on there roll equals navigation which is good it's good from an accessibility standpoint so let's go test this i'm gonna go copy our test room over here and create a new sidebar.test.tsx file and we'll bring in sidebar and now we want to render it with some items so we have our items gonna be a value which is an array and then within that we'll have name test sure path is test which is not path it's href and i actually want this to be its own array so i'm gonna cut this out of here and make it items and then create a new constant for items which has that array in it so how are we going to get all of the potential items well there's get all variants for these so for example get all by roll and in this case it's navigation and we'd have anchor elements which is going to be an array of anchor elements and we can say that the zeroth one should have test or at least items zero dot name right and then it's href should be items0.href so let's just set it to blank just to make sure that's actually getting tested and set that to the zeroth item all right let's see how we doing okay so it fails where we expect it to the href is not blank let's go and put in there items zero dot href and we're passing very cool okay so the next thing i want to show you is how to test for event callbacks like an on click i'm going to close up a bunch of these files and we'll create our own version of a button wrapper so i'm gonna create a new file called button wrapper dot tsx and let's go get ourselves a nice starting point again pop that in there and this is going to be a button wrapper and we're going to use that react.function component declaration in typescript so because we're wrapping button we want to pass through all the props of the original button so you need to use react detailed html props so let's do that and we need to tell which attributes we want so in this case that's the attributes of the button element and then you also need to specify button element and now we've got all the props and we can just pass those on to our button now let's say we also want to add a title so we can say have a title which is a string we could use children for this but we want a title so we'll add title here and pull that out and then this is going to be title like that so let's go test this out so i'm going to go back over to a test file and grab that contents and let's make a buttonwrapper.test.tsx file so the test we want to do here is to handle an on click so i'm going to put in here handles on click and i'm going to use the button wrapper and we'll give it the title of you know add item so it's happy already but we need to pass it in on click handler so i'm going to go pass it a stub function created by jest so create on click as just.fn and this is basically a function that is going to track how often it's called and what it's called with so i'm going to pass that to onclick and then i can go get that button by its well let's just do my text and give it the text of add item and that's going to give us a button element so now we can actually go click that button in order to see the on click gets called back so to do that i need to bring in fire event and i can do fire event dot click and give it the button element and that's actually going to simulate a click on that button and now how do i know that it's been clicked well i need to go and expect on that on click and say that it's been called well in this case one time that's certainly good enough so let's hit save and actually let's just change that to zero to watch it fail first and now it fails because we expected that it was called zero times and it actually got called once which is exactly right so we'll change that to one and now it passes very cool all right so let's start looking at state and hooks by creating a counter example and testing it so i'm going to close these up so i'm going to create a counter.tsx file and to accelerate this a little bit i'm just going to paste in a good counter this has a state value for the count it's got a button where you can click on it and add one to that count every time and then it's got a div that basically puts out how often that button's been clicked pretty easy stuff so let's go back over here to our button wrapper test because it's got that fire event that we're going to want to use to simulate clicking on that button and copy it and then create a new file called counter dot test.tsx paste that in there and we'll bring in counter now we don't have an on click or anything like that but we do know that the counter has a button on it and says add one so let's go get that and that's going to give us our button and then we need to go and get the element that's got the text so let's go do that create a div element which in this case we can get by roll and we know that that role was content info and so we're going to look to see if that div element has that text of the count and how is that formatted let's go back over here count is count makes sense so let's do count is zero we know that's not going to work because by clicking that it should be 1. so let's see all right yeah we expected it to say count as 0 and it turned out to be count as 1. so let's change that to 1 and now we can watch everything pass rockin and isn't it great to have that feedback right away because we've got this testing system that's actually watching the code as we test it's a great virtuous cycle if you really get into that there's a system called wallaby that actually will run these tests dynamically as you type it's really cool stuff okay so now let's get into some really interesting stuff with a use effect so we'll have a component that makes an api call so let's close this up and again i'm going to make a new component call this api component and let's paste in this api component that i have it's a react function component it's got some data that it gets back with the structure of a name there's a use effect that fetches from an api endpoint gets the response back converts it from json and then if the component is mounted it then sets that data if you're not familiar with this is actually a really nice way to safeguard against this fetch coming in late and then setting data when the component is unmounted so we start off saying that the component is mounted and we check to make sure that the component is mounted before we do a set data and then if the component is unmounted meaning this function this cleanup function is called then we set is mounted to false and now make sure that this set data isn't call now down here we only show this data if we have it so let's again use content info in there and let's go make our test so let me just pick off one of these tests copy it and then i'll make a new file api component.test.txt paste that in there and we're going to see that it gets the data so api component and what we want to do is test one that this data shows up and that this div is created so we kind of actually need to wait for things so this is going to be an asynchronous test and then how do we actually have an api well we don't have an api so we need to use a mock service for that so what i'm going to do is i'm going to use this library called the mock service worker this is actually an excellent excellent library and it's pretty much state of the art so the way that we do this is we basically set up a worker that will respond to this api so to do that i'm going to go import from service worker rest and setup server and then i'm going to use that setup server to create that api endpoint i'm going to say this is a get endpoint and i'm going to return name is jack so we don't want that server running all the time we want some lifecycle function so these functions are before all meaning before all the tests and then after all the tests in this which case we're going to use server.listen to fire up that server and then server.close when all the tests are done and then after each test we are going to reset the handlers of course there's only one test here so no way to deal with that now we've rendered the component but we need to wait for that api call to complete so to do that we're going to bring in wait for from the testing library and then we're going to use an await on wait for to get that element with the role that we want so in this case that's that content info element and that's going to give us our output element and we can go run some assertions on that output element we can say that in this case out should have that text content that we want so let's see text content and what are we looking for well we'll just start off with an empty string and see what it comes up with we don't need that cool okay so we expected nothing but we got name is jack that's awesome that actually did the entire round trip it used the use effect it made the fetch call it got the data back really great stuff so let's prep that in there and now we've got some running tests very cool okay so why stopping components let's go and take a look at how to actually test custom hooks because i think when you're doing react knowing how to do custom hooks is a really important skill so we're going to go create a very simple use counter hook and it's just going to have a basic counter in it so we've got a use date we've got to use callback and it's going to return the count and then an incorrect callback where you can increment that count pretty simple stuff so how do we test that we're going to use a different testing library for that so let me go make another file called use counter.test.ts and we're going to bring in our counter from that use counter file but we're also going to bring in some functions from a testing library specifically designed for react hooks in this case testing library react hooks and that's going to have a render hook which is going to render the hook and then there's also an act function which allows you to basically say okay now do the increment for example in this case so let's set up our test and say that it should increment create a function and then we're going to first call use counter by using that render hook let's try this out result is yeah okay here you go github copilot thank you so much for that so we want to render that hook and we want to start off with a use counter now that result has like the count and the increment in it which was the result of that hook and now we want to go and act on it so we're going to call act and then within that we say result dot current that's going to give us the current result we're going to increment that and then we can expect that the result.current.count to be well let's start off with zero and see if fail and then change to one yep there we go we expected zero we got one and now let's change that over to one and now everything passes so how do we do an asynchronous hook glad you asked let's create a new hook called use api that is basically the same thing that our api version did so this is going to do that same api call in fact this is just a copy and paste from that component and it's just going to return back the data so let's go and make a test for this use api.test.ts and we are going to bring in a bunch of this testing stuff from here as well as from that test component for the api so we need to set up that server and bring in the rest and setup server imports all right starting to look good okay so now let's bring in use api and we want to use api but we're not actually going to do anything with this so we don't need that but the data is now just the current result and we want that to be name jack yeah but we actually have to wait for it so how are we going to do that so the way that we do that is we actually get another element off this called wait for next update and we can use an async function here and then just await wait for next update and let's just make that empty and see that it fails yeah it got back name is jack and we had name is nothing so let's change it to name is jack and there we go and now we need to do is instead of saying to b we want to say two equal there's a slight variation between these one is an identity check and the other is an equality check so in this case let's see does two equal work yeah perfect awesome so you need to know the difference between when you're checking if the two objects are actually the same object versus the two objects have the same value okay so we've looked at react components asynchronous react components hooks and asynchronous hooks next thing to look at are ones where you have a state manager involved and the most popular state manager to some degree i would say is redux so i've already installed redux so let me go create a store and we can test out how to test a component that uses a redux store let me close that up onto these files and i'm going to create a new file called store.ts and i'm going to bring in the redux store that you get if you go through the redux toolkit walkthrough so it goes and creates a counter store that has increment and decrement i'll show you what it looks like here we got the counter state that's got a value that value starts at zero we've got a slice called counter which is the initial state that increment decrement increment by amount fun stuff and then we can get a store that we get from create store and then we got the root state type and the app dispatch we also have the increment decrement all that great stuff so it's a pretty standard redux toolkit store so let's go create a component that's actually going to use that store so i'm going to create a new file called redux counter paste that in there and that is going to use selector and dispatch from react redux and it's going to bring in increment and decrement it's going to use a selector to get the current count and then it's going to have dispatch with it and uses to dispatch increment and decrement actions and then there's going to be a span in there that has the current count so how do we go and test this one so let's go and create a redux counter.test.tsx okay so let's bring in some imports i'm going to bring in react and then render screen and fire event just like we did before and then that redux counter so let's test increment and we're going to want to render that redux counter and then we're going to want to go get that content info and expect that it'll have zero in there for the current count and then i'm going to grab that increment button and i'm gonna click on it and then we're gonna expect that counter to have a text count of one so let's see what happens all right so let's go back up to the top here and see what the issue is so we can see that we didn't actually put a provider on this so like every redux component it needs to have a provider somewhere to provide the store down so let's go and bring in that provider and then let's wrap that redux counter in it but we also need a store right so what are we going to put in there for the store so in this case we're going to bring in the store from the store pop that in there and now we pass awesome cool but it's a little more twisty right because redux is global which means that now the store is actually globally set to one so what are we going to do if we want to try this again essentially we'll say copy and paste the exact same test and say increment again let's see what happens well now we blow up because we expected at this point in the test let's go back down here we expect that the counter is going to have a count of zero at this point but it actually has a count of one because in the previous test we actually incremented that value so in this case i've exported create store from the store as well and that's the one that actually creates a store so i'm just going to call create store right here and create store down here and that's actually going to generate a new store for each test so take a look and now we pass and that's something you really need to pay attention to when you're doing these tests is to make sure that your environment is clean on every single run okay so now we've taken a look at redux let's take a look at zustand which is a popular replacement for redux and i'm just going to jump over here and take a look at the zoo stand home page because dang if it isn't the coolest page ever and we're basically going to use this zushtand store as our test exactly the same thing really honestly it's just a incrementer store so we'll start off with a new zuschdund store file and i'm going to paste in the zeus done to store and that's going to give us back a custom hook called use store we're going to use in our component so i'm going to create a new component called zeus don't counter and what it does is it brings in use store from zustan store and then all you got to do is just get count increment pretty easy and then we've got an increment button and we got the count you can see why people actually really like this a lot less boilerplate than even redux toolkit which is a lot less boilerplate than redux all right so how do we test our zeus done counter okay so i'm going to bring in render screen and fire event and also zeus done counter and i'm going to create a test for add one and again we will render that zushtan counter and we're gonna go get that content info element and expect it's had a text content of zero because we're starting off at zero then we're gonna grab that add button and click on it and then we're gonna expect that that counter would have one all right let's try it out and it works great but again this is global so what happens if i do this again add one again so we run into the same issue it expects that it's going to start off at 0 but it actually is 1. but there's actually a really easy fix for this i'm going to bring in the store from that zeus done store i'm so impressed the fact that i'm actually getting the pronunciation right i hope i am and with that i'm going to get the original state of that store so i'm going to say this is original state and then from there i'm going to do get state and that's going to give me the original state and then before each test i'm going to set the state back to the original state let's try it out and there we go work to treat all right well as usual all of this code is available to you on github for free and i would love to hear from you in the comments about how you do your react testing and anything that i might have missed and you'd like to see in another viewer suggested video in the meantime of course if you like the video hit that like button if you really liked it hit the subscribe button and ring that bell and you'll be notified the next time a new blue collar coder comes out that was a lot longer than i thought it was going to be a lot more to go through but i hope it works for you
Info
Channel: Jack Herrington
Views: 3,564
Rating: 4.986928 out of 5
Keywords: TypeScript/React Testing, how to test react hooks, jack herrington, mock service worker, react hooks, react testing crash course, react testing workshop, react typescript testing, redux typescript testing, testing async function typescript, testing custom hooks, testing react components, testing react components with jest, testing react hooks, testing redux with react testing library, testing typescript async code, typescript, typescript react testing
Id: bvdHVxqjv80
Channel Id: undefined
Length: 29min 37sec (1777 seconds)
Published: Thu Sep 23 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.