Testing Asynchronous Components with Mocks in Jest

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey how's it going everyone today we've got a video for you where we're going to be doing some testing and react so if we take a look here we've got this image gallery react app and we want to do some testing on it make sure it's working correctly but there's a few tricky things going on that we're going to need to figure out how to handle so let's take a look at this component and then figure out what's difficult and how we can solve it so if we look here we look we have a component didn't ount that calls fetch images now what this function does is a it's a synchronous and B it makes an external Ajax for call to the unsplash API so that's what we're gonna figure out how to test in this video how to solve sort of asynchronous react components and make sure that they're working fine and how to mock this call so that it doesn't make the real API call but instead uses a fake version of it and so what this does is it it updates the state to say that we're searching and then it awaits the response from unsplash and it updates the state again with the the results the images and otherwise it catches an error so down here in the render we're not going to spend too much time here specifically but basically it Maps the images that are in the state and it outputs an image component for each one of them so let's see what we can do first to get this test incorrectly now our first problem is that this is an AJAX call and it's very hard to test this in its current format so we're going to refactor this call basically this a weight Axios get I'm going to cut it and we'll come back to this code in a sec and we're going to move it to its own service basically just a function that I'm calling a service but we're gonna put in it's in a new location so we'll come over here we'll create a services folder and we will create one called unsplash dot J s so in here we need to import Axios because that's what we're using to do the Ajax request and then we'll export default an async function which will receive the term and then we got a little arrow function here where we're going to paste our actual Ajax call so the terms received it does this so we want to put this into a variable cost response and then we'll await for it and then once we've got that we can return response data in the Axios this is how you access the body of the response and it's got something called results in this case it's an object that has a results array that's basically one for each image so now that we've got this here in its own little function we can use that instead of having this call in line in our component so we'll go back here we'll import it first so import unsplash from dot slash services on splash like that and then we'll come down here and now we're not just getting a response we're actually returning the images right away and we'll call our unsplash function passing it the term that was sent to us here so what we're doing in this app is basically when it loads we're searching for mountains right away so that the user gets to see something right away on the screen and so that will be the term essentially mountains will call the function will get the response and put the images into our state so let's just load the page hope we've got an air images dot map is not a function why is that oh I know why because this returns a promise so we need to its an async function meaning it returns a promise so we need to await for that promise to be resolved and give us the response so now it's working correctly we could search for alam as if we want and it seems to work fine so now that we've done a little bit of refactoring on this app we want to actually test our app component so let's go in here and we'll write a test so new file app test j/s and the first thing we need to do is import our app component import app from App just like that okay so what we want to test is that it fetches images from unsplash and renders em on mount a little bit of a long name but it's very descriptive and then we'll do our arrow function here okay so here's where it's gonna get a little bit different and we're going to use something called jest mocks and we're gonna have to deal with the asynchronous nature of this code because if you come back here and look at the app so when the component did mount it calls fetch images but we don't get a response right away from the unsplash API it returns a promise so we wait for it and then update the state so if we want to test that it works correctly we need to deal with this asynchronous nature of our code and the first thing we need to solve is that in our test we don't actually want to hit the unsplash api because hey that's gonna make our tests really slow you're not always able to hit the real API when you're testing like say you're making a payment or something like that you can't actually make a payment every time you run your tests so basically we need a way to make a fake version or a mock and other terms of this unsplash function so that's what we're gonna do we're gonna come back here and before we even fill in this part of the our test we're going to use Jess Mach and we're gonna tell it which module in this case we want to Mach because unsplash lives in its own file and it exports default so essentially it's a module that's being imported elsewhere into our app component here so what we need to do is just stop Mach and we need to point it towards the service basically that we're trying to mock so we go up a level gets us two components and then we can go into services and then into unsplash okay so this is what we want to mock but we haven't actually built the ability to have a mock yet so what you do in jest is you come over here to the services folder and we create a special folder called underscore underscore mocks and in it we create a module with the same file name so I'm /j s so now in here we can create a fake version of the real version of this function here so we want to have it follow the same sort of signature so it exports default and asynchronous function which takes a term and it returns some it waits it waits for a result and returns some results so what we'll do is we'll say export default and async function which takes a term and then we'll start filling in this code here so if we come back here we see that it awaits for something that returns a promise and when this promise resolves it essentially gives us the images so let's just follow the same signature of what this does so we're going to say that we've got Const response equals we're going to await for a promise to resolve so we'll just create a new promise and this receives resolve and we write our code here so then we need to have this promise resolved so we can call resolve and basically pass it the images here so let's create some fake data then and we'll have it resolved with this fake data so we've got Const fake data and it will be an array and we want to have this fake data look like the real data that's returned from the real unsplash API so if we come over here to app and sort of follow where the images are being used we can see that each image is mapped and passed into the image component and right away we can see that each image has an ID so we'll give it an ID of one just in our fake data here and then we'll open up the image component and we'll see what it needs so this is where it would have been much better if I had proper prompt types maybe you know why don't we do this at the same time so image is an object which is what I have here but it doesn't tell us anything about the shape of this object so we'll say that it has a shape and what it looks like is that each image has it looks like categories which is an array that is an object that has a title so we'll say it has categories which is prop types dot array of and it's an array of a prompt type stop which is an object that has a title which is a prop types dot string okay it's getting a little complicated now this prop tapes thing we can see that it has a user which is also an object so shape and that has a name so we'll do prop types dot string as well here so now we've covered this code here it looks like it has links dot HTML so we've got links which is pop types shape and that's got HTML which is a string we're almost done we've got an image has likes which is a number and URLs which is an object that has a small URL so URLs is a prop types shape which has a small which is a prop type string and we've got likes which is a prop types dot number cool so this is basically what an image looks like it's got a an array of categories each category has a title it's got a user links URLs and likes so we need our fake data to mimic this so I'm just going to copy this over so I can remember what it looks like and we'll say we've got categories which has an array of an object that has a title just a nice image we've got user which has a name mr. photographer we've got URLs which has a small one which is we'll just take it to my website holiday calm we've got oh no will do links and then URLs is the actual image which has a small doesn't really matter so just image comm slash nice Jeep last one is likes we'll have ten cool so I can remove this and now we've got our fake data and we're resolving this fake with this fake data which gets sent to our response and then we'll just return our response here so we could shorten us even more by just returning this here so just to explain this code because it's a little bit dense we even asynchronous function which is going to return whatever this promise results which is our fake data up here an array of images so we're going to wait for it to resolve and then return what it gives us so now we've created a fake version of the real unsplash function so let's go use it we come over here to the test for writing and the first thing we need to do is because we're not testing synchronous code we have to receive an additional done parameter which is a function you call once this test is done essentially because it can't sort of wait till it gets to the end of the function because things are happening asynchronously out of order so we just call done when it's ready to have been finished so the first thing I want to do is essentially shallow render using enzyme this component so what we'll do is we'll say wrapper equals shallow app so now we've got a shallow render of this app shallow is a function which comes from enzyme the reason I can use it is because I've got some test setup that imports shallow from enzyme and makes it global right here so that way I can just use it in my tests without having to import it and whatnot and now I need to basically test to make sure that things are working correctly so let's say that we want to use our wrapper and test that it's rendering one image because that's what our fake data consists of so we come over here and it's got an array of images so it should render one of these image components so what we can do is we can say expect wrapper dot find image length two equal one so we'll run this test it's going to shallow render this app and we're going to make sure that it finds one image so we'll get out of here and we'll we'll run our tasks we wait for it to run and it fails it says it's received zero but we know because we mocked out our API call it should be returning one image here so there's a reason for this and the reason is that even though we mocked our function it's still promised which means it still happens asynchronously so we basically need to wait for that to finish that promise to resolve and then we want to do our test and there's a little bit of a trick you can use to make that happen essentially in JavaScript we want to wait for the next process tick and what you can do for that is you can use a set timeout so we'll use a set timeout except we won't provide sort of normally when you use these you say like run like 1 second later in this case we don't want it to run second later we just want it to run as soon as possible but the way JavaScript works it will wait till the next tick and by that point our promise should have resolved itself and then we can make sure that it's equal to one so we come back here and we check and it's telling us that the async callback was not invoked within time-out specified by jasmine default timeout so there's a couple things we can do to solve that one thing we need to do is we need to tell just when this test should be done so we do that by calling the done function and it's still going to fail and the reason it fails is because we just shallow rendered this app once and because things have happened the state has updated we need to basically explicitly tell our shallow rapper to update itself to re-render so that we can find the image and make sure that there's one of them so what we can do for that is we can say rapper update and now our tests pass so we come in here we import our app we tell it to don't use the real unsplash service use the mocked version of it and we uh shallow render our app but we write our code in a timeout so that we wait till the next tick when all the promises would have resolved and then we can tell it to explicitly update to re-render and then we'll go into the wrapper find all of the image components and get the length of them which should be one because that's how many images are in our fake data so let's test one more thing let's say we want to make sure that it's set the state correctly because if you come back here to this fetch images function it's change the status to searching and whatnot but once it's finished waiting and it's returned the result here it should have a status of done and in the state should be all of the images it found so we can test that as well and the way we do that is we'll put the state in a variable and the way we access it is getting the instance of the wrapper and then we can access the state so the instance is basically the instance of the app component itself and once we have that then we can go and access its State so now that we have the state we can just write some tests to say that we expect the state dot term to equal I think it was mountains still passing just rerun them to make sure we can expect the state status to equal done and we can expect the state images lengths to equal one and it's all pass incorrectly rerun it it's good so essentially what we've done if we just step back to the beginning and walk through this process again quickly we took our app component that was making an AJAX call sort of within this fetch images function and we extracted it we extracted it to this unsplash function that returns a promise of and eventually it will give us the images once it's resolved so we go over here and we just created a little function which receives the term waits for the Ajax call to finish and returns the images accessing it through response data at results by doing that by putting this function into its own module now we can mock that module in our test so that we don't use the real one we use our fake version of it so we did that by importing our app and then telling Jess that we wanted to mock that module and when you tell it to mock that module just looks within the same folder that you pointed to but it looks in a mocks folder and that's underscore underscore mocks lowercase underscore underscore and it looks for the same file name here so in here we basically created us a similar version with the same sort of signature and way it performs to the real one so we exported an asynchronous function which receives the term and it's going to return once this promise resolved so it's going to wait for it and this promise since we created sort of a fake promise that resolved instantly it resolved with our fake data which is up here so now that we have that we can go back to our test we can shallow render our app but because it remember things happen asynchronously we can use this little trick of putting our code in a set timeout and that will make it happen at the next tick we can force our component to re-render and then we can use this wrapper instance to get the state make sure that it's set correctly and then we can go in and find the image component and see how many it found because it always sort of returns like an array of however many it found and we can check that it's equal to one because that's how many images we have and because our test happens asynchronously we need to pass a done function to this error function for our tests and explicitly call it when our test has finished running and that's how you can test asynchronous components in react ingest by mocking the module and creating fake versions of it I hope you enjoyed the video take care bye
Info
Channel: Leigh Halliday
Views: 31,775
Rating: 4.9784946 out of 5
Keywords: react, testing, jest, async, mocking, ajax, asynchronous
Id: uo0psyTxgQM
Channel Id: undefined
Length: 23min 21sec (1401 seconds)
Published: Fri Apr 06 2018
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.