Testing a Screen with React Native Testing Library [Live Coding]

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
what's up folks spencer here with react native school today i just want to walk you through the process of writing tests for a screen so we've got the sign in screen here and if we look over at my test file i've just got it set up to make sure i know i've got uh jest and react native testing library testing library slash react native setup i've done no other prep for this so to preface this if you're looking for something refined and super uh value for you know every second you're getting this isn't going to be the video for you if i could figure out how to live stream this would have been a live stream but i just want to walk you through the process of going from i have this screen it does some stuff and i want to write tests for it to make sure it does that in the future walk you through that entire process no idea how long it will take but with all that those warnings out there let's go ahead and get started now like i said we're just going to be focusing on writing tests for the signin.js i use a few other components in here and we can cover that stuff but we won't be doing that in this video today we're just focusing solely on this login screen and making sure its functionality continues so as i said i do have just set up already yarn test uh we can set that up and we've got it watching for any tests so we know our setup is good we can just focus on writing our tests here so first thing i want to do figure out what am i actually testing here and styles doesn't really matter our use login forms state now this is a custom form or a custom hook that basically just manages the username password submit state then it determines if a password or a username or password is valid or not in this case because it's just an example the only way to have a valid username is to have it set as example only way to have a valid password is to have it set as asdf i've got some stuff set up here and then finally we've got a submit function that when you submit if both the username and password are valid we'll go ahead and make a fetch request to a sample api it does nothing but once that completes we'll go ahead and push a new screen now maybe think okay i need to write tests for this and you could but in the case of this there's no reason for me to write any tests related to this form state because basically everything that we see here or the output of all of this will be represented in another way we'll go through and write all of our test descriptions here in a moment but just to give you a quick rundown on what this looks like so if i were to press login now we're going to see our form show errors we've got invalid username and invalid password if i type in a valid username it'll go ahead go away likewise if i type in a valid password and i hit log in we'll go ahead and go to a new new screen so these are all the things that i want to write tests for for this screen okay so looking down here basically we've just got these inputs and then we've got this error text so this is all kind of what we're looking to test so first things first you can see i've got this it renders example but basically first thing i want to do let's actually go ahead and refresh this so we start at a clean slate okay it's freezing on me okay we're good here so first thing let's make sure it renders the default elements oh this thing is having an issue okay so we're going to write a test it renders default elements and we'll go through and fill in all these tests later on next thing what is going to happen if we've got an invalid username we want to see that it shows the invalid username if it's invalid shows invalid username error message shows invalid password error message actually let's just make this one test shows invalid input messages then let's also set it up so that we've got individual tests for each one of these so that if we just have an invalid username but we do have a valid password all that uh what else let's see so working down we've got our default state we're just making sure everything's actually rendered we're checking our username and password if we've got those errors next thing okay so on submit what happens from a user's perspective what are they going to see on submit so when we hit submit if they've got a invalid username or invalid password they're going to see error messages we've got we've stubbed out tests for that if it is valid though we want to make sure fetch is called so let's say it handles valid input submission okay and then we'll go through and fill that out well i'm going to want to make sure we've got a fetch uh we go into response.json and then we also do a navigation.push okay i think we've got the initial test i want to write here okay so it renders the default elements let's go ahead and fill out this test okay i've got my terminal running or my tests running here so let's go ahead and write these tests so first thing we'll make sure all the elements we're looking for are on the screen so i want to go ahead and do destructuring off this render and if you're not familiar with the testing library react native api i've got it pulled up here but basically we've got get by get all by query buy and find buy i want to use uh get buy and get all by because i want to make sure those elements actually exist because we're looking for them so what we can do is say we've got login here and we've also got login the text down on the button so let's go ahead and grab a get all by text and i want to make sure that get all by text of login we want to make sure that the length of that is equal to 2. that 2b two so if we look at our test it's running cool that works if i were to remove this first login our test should fail wonderful it does so we're making sure we've got that log in there is that a super useful test that that's up in the air but it is there so next thing let's make sure our inputs are actually rendered and rather than using like a test id for this i like to use the get by placeholder input because the user is actually seeing the placeholder so we've got this example placeholder or these three stars placeholder so i want to use the get by placeholder text i actually just want the get by so the difference between get all by and get by is get by will return the first element get all by will return an array of all matching elements there's only one instance of each one of these inputs so i'm just going to use get by text get placeholder get by placeholder text so get by placeholder text i expect example to show up and it is and then i also expect my placeholder text of three stars to show up now we could also do an assertion that username shows up and password shows up just to make sure that those are the right labels showing up but basically i just want to make sure these elements are showing up and i don't care about all of the implementation of this input that can be tested on the input component itself that it has this api that i care about i just want to make sure that my inputs are being rendered because the inputs where the user is actually entering data on this screen so i'm targeting kind of that underlying component that the user is actually going to be interacting with on this screen which is the text input okay so i think that should be everything so if we look at our tests it is passing so i think we're seeing that all of our default elements are working now let's do a show shows invalid input messages so i'll set it to set up my test we're gonna do the same render this time so if i were to press this button simulate what's going to happen here oh it's freezing on me again i don't think my computer is happy about recording and running the app at the same time okay i'm going to go ahead and restart this like i said this is a raw video there's no editing going on so while that's happening something we're going to have to do here is actually interact for and we're going to need to grab the fire event event so i'm going to import or fire event function from the core library so i'll go ahead and grab that okay so if i press this button again you can see we expect these two messages to show up invalid username and invalid password so what i want to do i need to figure out a way to target this button and previously we had looked just for the text but this time what i want to do is actually i'm going to use the test ids just because i can target this button in particular because it does have the same text in the header and the button so i'll go ahead add a test id i'm going to say sign in dot button so let's go ahead destructure this again and i want to get a get by test id then i want to press fire event get by test id actually this should be a fire event dot press it's a press event and i want to grab the sign in dot button see what happens okay so we're seeing no instances found with test id what i'm wondering is if this button component is not actually forwarding down that test id that i passed so it isn't so i'm going to make a quick modification to this component i'm just going to go ahead and grab any remaining props and just tack that onto this touchable opacity so dot dot props so i'll just spread those remaining props down onto it now our test should run okay so now we're able to actually interact with this specific button next thing we want to do is after we've run this we want to make sure that our error messages show up so we'll also grab get by text and i want to make sure that get by text invalid username i think that's the error message yeah invalid username and invalid password show up let's see this test run okay cool so this test is passing if both inputs are empty next let's go ahead and write a test where we actually type in some values here so we're interacting with our inputs so it shows invalid user name error message so i basically want to do exactly this but before i press this button i'm going to type into my input so let's figure out what that api is for fire event is there an easier way okay so there's convenience methods for press change text and scroll so we'll use change text what we'll want to do is grab the placeholder input is there a better way that i could test this because basically this isn't very descriptive and this could change i don't want to have to update all of my tests if i change the placeholder text because it's not super important and i've got one test making sure that what i expect the user to see is being seen so now that i'm just testing the interaction i think i want to do a get by test id for this password input so what i'm going to do is get by test id i'm going to call this sign in dot password input then i'm going to want to do a fire event dot change text so we target the element first and second i want to add an input i'm going to add an a valid input here which is asdf fireevent.changetext we're getting this input when i press the button we should see the message of invalid username still but then if we do a query by text i don't want to see invalid password so i expect the length of that actually i should do a query all right query by text.length let's try this is that 2b 0. i need to wrap that in expect the reason i don't wrap get by text in an expect is this is going to throw an error if that doesn't exist so i don't need to add an assertion through just to make sure that happens i'll get an error anyways so let's see what happens this test is going to fail because we don't have this test id yet but let's just double check okay it is throwing an error and it's showing on this line no instance is found with sign in dot password input so let's go ahead add that add a test id equal to the sign in password input let's make sure input passes that down it does we can use that same props betting spreading we were using before okay and we're seeing a type error so slow down their spencer uh basically we're seeing we're now interacting with our input we're pressing the button and now we're seeing the error thrown on this query by text it's telling me cannot read property length of null and basically instead of query by text i want to do query all by text so that i get an array back of an empty array because there shouldn't be anything that passes that matches invalid password and i did not import query all by text so let's grab that okay cool and if you ever want to just like you know figure out if you're actually doing things right you can go ahead and grab a debug and if we drop that in here you can see what that rendered output is that react native or testing library slash react native is actually looking at to make its assertions so if we look here we can see our login button and above that we see our error text we do see invalid username we don't see invalid password anywhere we see our inputs and we see our header text so we're seeing everything that's going on there so everything looks right um and as i write that i actually want to expand on show's invalid input input messages um right now we're just testing if basically if this works for an empty input do i want to add a test based on if the basically it doesn't pass the client side validation so it's possible it couldn't pass the server side validation what about the client side and client side validation here is basically is it equal to example or asdf and another example could be is the username or password long enough to pass you know a known set of rules that you just put on the client side so you don't have to wait on the server let's go ahead and add one of those let's see how do i want to do this so we'll do the same thing we've got down here let's go ahead add a test id to our username input test id is equal sign in dot username input okay so what i want to do now let's just copy or would it make more sense to go down in here okay shows invalid username error message okay it actually makes more sense to go down here so we've entered our password input we've got a valid password we press the sign in button we get by text invalid username and we expect invalid password not to show up let's go ahead interact with this input again or with our um username input let me grab that so let's interact with the username password and i'm just going to type in invalid input right we know that's not going to pass our our validation per se this time i still expect invalid username and query all by text to be there or invalid username to be there but i still don't expect an invalid password to show up let's check our test running perfect looks good now let's go ahead and do the exact same thing for our password input so you know we're going to need to render our component we're going to type into our username input this time so go ahead and type into my username input this time i'm going to enter a valid input which is just going to be example next if i were to go and press the button the submit button let's go ahead copy this over this time i expect invalid password to be showing up but i expect invalid username to not be there so if i run this test okay that's passing let's go ahead and do the same thing we did here where we actually type into the input to make sure validation still works fire event change text we want this to be password input i'll still leave this isn't right password input i'm still going to leave invalid input as the input to that because it's still invalid then if i run this we can see our test fail no instances found with text invalid username oh okay so our test is working it's telling us that there are no instances of invalid username because there aren't there should be an instance of invalid password error text shown and then if we switch this one around to invalid username dot length to be zero our test should pass and it does so we've got all of like our are we rendering what we expect are we doing our client side validation we've got those tests showing up and you can see everything i'm doing here is basically what is the user actually seeing on the screen so if they were to type in invalid input they're seeing invalid username showing up i'm testing that they see what i expect them to see versus kind of like how everything works so with that all said we've kind of got the client side stuff done what happens when they pass in a bunch of valid data and we go ahead and then uh submit that what happens then that's what this handles valid input submission is so handles valid input submission we know we're going to want to render our component and we need to type in valid input so i'm going to go ahead and grab this stuff so we need to type in a valid input for the username the password input so example in asdf and then we go ahead and submit when i run this test it should pass because it's not testing anything at this point oh okay actually it's throwing an error reference fetch is not defined so we actually need to mock this which i forgot to do before this so i'm going to look up just fetch mock go ahead and install this yarn add dash dash dev just fetch mock um we're going to create a a setup jest file i'm just going to call this setupjust.js in which we're going to return fetch mock enable that and then i need to actually run this before my jest setup occurs and i can do that in setup files after env this is all in the documentation um actually it should be just setup file so i'm going to go ahead and copy that paste that into my just config let's say yarn test dash watch so basically with that i just i mocked out the fetch api that we know through basically web development or react native development okay so we're seeing a new error that's cool let's start working with that so we're pressing the button what we need to do now is actually look at what what is our app doing so if we fetch so we make we submit this everything's valid his username is valid his password is valid we're hitting our fetch api uh basically we need to mock a response for this so what we can do again going over to the documentation okay we want to mock our response once so let's go ahead and say fetch dot mock response once how do we do this okay so i need to stringify a response that we're going to use so i'll just go ahead and tack in a json.stringify pass is true that's all i don't care what the response is in this case i just want to fetch some or mock something so that our test should pass now okay cannot read property push of undefined so we're getting new errors let's go back over to sign in js oh don't know what i did there okay sign in js so we're doing our fetch going on okay we have this navigation.push this is perfect this is something we want to test that if we get through all of this we want to make sure we actually push to our app so let's see we need to actually mock that and if we look at this if we look at our component we're expecting a navigation prop coming into it so we can go ahead and mock that what i want to do is say push mock is equal to jess.fn that allows us to run assertions against this function so i can say navigation is equal to an object and this is you know you kind of have to be familiar with the react navigation api to know this but navigation is going to pass an object down we're calling the push method on that so we can go ahead and just pass in that push mock to it so now if we run this i think it shouldn't give us an error okay it doesn't but now we can start running assertions against this so if we look here we're successfully mocking our fetch and once we've successfully gotten a response back from that we expect our navigation.push to be called with app so if i take a look at push mock lg push mock okay we can see all kinds of different stuff here basically what we're interested in is this dot mock so i can say push mock dot mock we should get a calls array in here if we look in dot calls oops it should be with an s okay push mock dot mock dot calls i'm not seeing anything so that that's actually an error let's go ahead write an assertion against this expect push mock dot to be called with app right because i expect push to have been called with app to be called with is an adjust assertion uh yeah basically this is something you'll learn as you write more and more just off these different apis that you can use so i look at this okay we expect it to be called with app but it never gets called what's wrong here well what's happening here is we're entering our inputs we're pressing the button and we're immediately running this assertion on our push mock the problem with this though is that if we look at our sign in this is a promise right so it takes some amount of time it's basically instant but it's not basically within the same tick that our test is running with so even though we expect navigation.push to have been called it has not yet been called at this point so we need to basically wait for our promises to resolve before we actually make this assertion here so first thing we want to do is actually turn this into this test into a promise itself we can do that by just prefixing the actual function definition with async allowing us to use async weight here so now we need to figure out what do we await for and i don't actually remember so let's go to the api and see okay what is wait for i think wait for is going to be waiting for something to show up on the screen and unfortunately nothing actually shows up on the screen once we wait for this so this one isn't going to quite work for us i think the other is act um let's see that might work actually let's go ahead and google it wait for promise to resolve just there's a way i used to do this react native testing library okay our async utilities it's deprecated it's a loud alarm okay um so this is all deprecated i don't want to use that okay um i wrote some code recently let me find it promise this won't take long so basically we want to simulate waiting for that promise to resolve the way we do this looking through the react native community slack uh because i know we covered this recently okay here this is what we need to do like i said this is unplanned stuff okay so we want to use this act that we saw defined over here so we want basically we want to wait for something to happen and we want to basically i don't know basically we want to wait for our test to resolve uh for our problems to resolve i don't know how exactly to describe this basically uh let's go and just write it maybe i can explain it better then so i want to await act which i need to go ahead and import from testing library slash react native now what am i waiting for here well i want to wait for a new promise and that's going to take a resolve function basically if i set immediate and pass in that resolve function so it's called immediately i still don't know how to explain this but basically this is going to wait for our promises to resolve which if we see here we've got multiple promises resolving before it actually does this assertion so we're going to wait for this chain to complete before we run this test and this is like something that i pull into a function and i have set up in my tests so if we look at our tests it should be passing now and it is so i hate that i don't know how to explain this but you know i didn't really plan for this like i said this is going to be a live stream if i could figure out how to do it um basically this is a really useful utility function for whenever you're working with promises and if you've used react nature testing library in the past you may know this as i think it's flush flush micro task queue okay yeah so it's just not uh allowing us to import it it's it's a deprecated api but it's still super useful and i think the reason it was deprecated is because this is all it was you can just define that in your app so what i always like to do is grab this flush micro task queue i'll create a function in a file i normally import within my tests i just go ahead and drop this in here flush micro task queue and then i just drop that into my act so i just wait for that my test should still pass whoops this is going to cause an error um it's already been declared did i import it okay yeah so you can import it i'm pretty sure that api has been deprecated so i always just define it myself but flush micro tasks and that's flushing the task or the queue of tasks that queue being our fetch converting that response into json and then finally our push so of went off the rails here at the end but we're waiting for our response let's make sure it actually runs i think i've broken things now oh i was looking at the wrong thing okay our tests are passing here so we're making it through this chain uh oh geez okay we've made it through this chain of tests we've added our inputs when we press submit we're going through this chain we're making a fetch response or fetch request we're then converting it and then finally we're checking that navigation.push is called actually another assertion we can make is making sure it's actually making a request to fetch to the to the right url since we're mocking the fetch library we can go ahead and take a look at fetch.moc.calls and we're doing this before flush micro task queue because this is happening before any promises start if i run that and take a look at it we can see we are making a call to slash users so let's say we expect our fetch api dot to be called with let's see if i can just do a regex here we expect it to be called with backslash users yeah we won't use the the root level api but let's see if we can just use a regex to look if it's calling slash users okay and to be called with doesn't work like that i'm sure it does somehow um let's look it up to be called with regex okay we want expect string.match i i actually don't know regex so i'll just trust this slash users let's see if this works oh okay so it's okay here this is another way i cheat uh basically because a fetch is passing a bunch of stuff we're passing a url we're passing a method we're passing a body i don't want to have to test all of that i don't want to have to define all that so i like to just say expect fetch.moc.calls that to match snapshot and that should go ahead and write a snapshot for us which it did let's go ahead and look at that okay now we're seeing basically what what are we getting from this snapshot we're getting a request url and the body that is actually going along with it so um i think we're good here is there anything else i'd write a test for so we're testing our client side validation we're showing our errors we're testing that when we submit a valid input going and making a request to the right url we're passing the right method the right body we're converting that response then we're calling navigation.push to our app so everything's running here what if joe schmo comes into my app and he says post is stupid this should be a put well if i put this in here maybe the api doesn't support a put it only expects a post now if he changes that we can see our automated tests fail that's something i've missed in code reviews put a post change to a put but our tests are showing you know this is an issue let's investigate this further so if i change this back to a post that works or maybe a username instead of passing the username i accidentally pass a string of username well if i run this test again we'll see that our snapshot's not matching once again test is showing us that it's working correctly we're not passing the data we expect to this api it's not being passed correctly or maybe um joe schmoe once again is in here changing the set function and instead of yet set user name he's passing it to just an anonymous function in there so we've got all of our test passing if i save this that implementation of set for username changes our test should fail and that's because we're expect query all basically this is our client side error message shows invalid password error message we're seeing an invalid username error message when we don't expect to why is that well we can find this here so these are all the tests or the baseline level of tests i'm going to write for something like a sign in screen in a react native app if you've got more questions this i think is a subject i really really want to go deep on through react native school i'd love to hear your testing related questions on react native so feel free to leave a comment or reach out to me on twitter become a react native school member and we'll talk about this kind of stuff on slack that's kind of what inspired this video if you found this video valuable even though it's very raw and uncut go ahead give a thumbs up consider subscribing and yeah check out react native school i hope you found this valuable and i'll see on the next one
Info
Channel: React Native School
Views: 14,419
Rating: undefined out of 5
Keywords:
Id: VuNPrFH9H0E
Channel Id: undefined
Length: 40min 21sec (2421 seconds)
Published: Wed Feb 24 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.