Don't Mock Fetch (or Axios): Use Mock Service Worker and Test Like a User

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments

Thank you for the insightful video!

👍︎︎ 2 👤︎︎ u/RedlightsOfCA 📅︎︎ Jul 06 2020 🗫︎ replies
Captions
hey how's it going it's lee halliday and today we are going to talk about testing code that makes http requests for starters you want to try to avoid making http requests in your test because a it's slow b it's unpredictable what if that server's down do you want your test to fail and what if the values change for example the weather it's 20 degrees today 15 tomorrow your code can't really predict how many degrees it's going to be outside but lastly you may not want to actually call that server every time you run your test for example what if you're testing sending money between bank accounts or charging a credit card etc so typically what i've done in the past is i've mocked out the underlying package that makes that http request like fetch or axios sort of swap that out replace it with a fake version that just responds with a canned sort of prepared for response can't see dodds the the maker of testing library and a ton of other packages in javascript and react he mentions that he no longer recommends mocking fetch and axios and instead he recommends using msw which stands for mock service worker so that's what we're covering today how to write tests using mock service worker to avoid making actual http requests uh let's start by talking about what is a service worker so this handy diagram i found here when you think about your code making an http request you're sort of starting in your application over here on the left and you call fetch sort of in the browser or a node it goes across the internet this represented by this network here and it talks to that external server which receives the request and responds back to your app with whatever data now what a server worker does is sort of plops itself in the middle sort of like a man in the middle attack you could say and what it does is it intercepts that request before it goes out to the internet and then it can decide what to do do i want to check if i have a precached value so that we don't have to waste time going over the network but in our case what we're going to do is basically intercept the request and then just respond with whatever value we want so that we can avoid making that external http request what this allows us to do is sort of set ourselves up a step higher from sort of mocking out sort of low level packages and whatnot we're treating our tests more like how a user would use our code which is always a good thing so keep this in mind we're plopping ourselves in the middle between our app code making the request and we're intercepting it before it goes out to the network over here so what code are we testing this is sort of a setup of the test we're going to be writing so we're importing a function called convert from this file currency and we're testing to make sure that it converts correctly so when you pass in sort of give me the usd to cad conversion rate we expect that it equals 1.42 so i haven't set anything up yet so if i were to run this test it's actually going to make the real http request and i'll show you the unpredictability nature of it so we come back here and we expected 1.42 but we actually received 1.34036 etc so we can't really prepare for that right and this is where we are going to put that service worker in there to intercept the request and respond with sort of our own server response of 1.42 so the first thing i need to do is import the packages that we are going to be using so i've already added them to the package.json just go down and find them so the first one is this what wg fetch so we don't actually need this in our specific use case but what it does is it polyfills fetch let me just stop these tests it polyfills fetch because node doesn't come with fetch out of the box so what this will do is ensure that your environment has fetch installed in create react app that's not necessary because i think they polyfill themselves but doesn't cause any harm next thing we're going to be doing is using the msw package mock service worker so we're going to be importing two things from it the first is something called rest which will help us mock out restful requests which is what we're doing but they also have one for graphql that you could use if that's what you're doing and we're also going to import a function from msw node and what's it called setup server that's it cool so we'll use that setup server to create ourselves a server and this will be server equals setup server and what you pass to the server are a number number of request handlers each of these request handlers basically looks for one url or one pattern and it intercepts that request and then it can handle how it should respond rather than going out on the internet and actually making that request so let's set up our first one now we'll use rest and we're going to intercept a get request and the first thing you put in here is the parameter is the url your your wanted to look for to intercept so if we go over to the actual code i've actually got sort of two twin functions that do the same thing one uses fetch one uses axios and this is just to show that it doesn't really matter which one you're using mock service worker works for both of them so i'm exporting the fetch version as this convert and what fetch does right here is it makes a get request to this url if the result is not okay it throws an exception request failed with status code of 404 or whatever if it is okay it comes down here we parse out the json and we access the rates and we get the conversion rate for the destination currency axios is the same get request to this url we wait for it since its promises and then we return the data we want axios raises an exception if the request has a 404 or a 500 or whatever so that's why i added it to the fetch version to sort of make them uh act the same uh but yeah we're going to be starting with the fetch version so that's what it does let's copy the url you don't need the whole thing it's sort of pattern matches so as long as you have sort of the part of the request that you need to make it unique from the other ones grab the whole thing one sec paste that in there okay so the second thing you pass to this function is a callback and this is where you handle how the server should respond so you're given three things the actual request this response function you calls to trigger the response back and context to help you build up those responses so we will return the response and to it we use this context to set the status of the response so let's say it was a success and the second one will be the json the body of the response so we wanted it to look like rates and then the destination and then let's set it to 1.43 so we can check to see if it's actually working and if we run the tests again we'll see that it's still not using it so it's still going to be the 1.34 or whatever and that's because we need to tell the server to start listening so we can do that using some jest callbacks so the before all runs once before and will tell the server to listen we can do the after all to tell the server to stop listening to close itself server.close and then we can do the be uh the after each and what we'll do here is when we set up our server we give it a bunch of request handlers but you can override them and prepend them in each of your individual tests what we're doing here is we're basically resetting it back to the original state whatever was passed to this function here so this is reset handlers like that there we go cool so with these in place the server should be running and we should see that um it will fail but it will fail with 1.43 which is what we've got so now we can just update this to 1.42 and we are good to go just wait for that to finish great so let's work on a test that handles failure so our test will be it handles failure and this is async because we've got promises going on and what we're going to do is override our normal request handler so why don't we just copy this for now so the way you sort of prepend additional ones is you access the server and you tell it to use one or more additional request handlers and it adds those to the beginning of sort of the list it steps through and what we want to do is switch this from a 200 let's say to a 404 and we'll just we won't even send a json response we'll just do the 404 like that so in this case what does our code do when it actually fails it throws this exception so how do you sort of test for whether an exception was thrown inside of jest one way you can do it is you can say expect and then you call the code that should raise an exception so convert let's convert from like fail to cad and promises you always have sort of two flows positive and negative success or failure so the positive is when you resolve the promise the failure is when you reject it so what you can do here is you can access sort of the rejects and then you expect them to throw and then you can sort of put in text here that you expect to be in the exception message so let's put gobbly goop just to test the things are working right and we have to await this because it's asynchronous so if we run this again we'll just wait for this to finish up our test should fail and we were expecting fdf dses and we actually got requests failed with the status of 404 so that's what we actually wanted so let's just replace this with 404 and this test should be passing now cool so one thing i thought would be a good idea is you never really wanna if what if you forget to add the proper request handler now you don't want the actual request going out onto the internet so what you could do is add like a fallback um request handler we can just say get request anything star call this request handler so we've got the request the response and the context like we saw above what we can do is we can say console.error please add request handler for and then we can access the requests url convert it to a string and that will be printed out to the console we can also return a response that just is a 500 error so like a server failure and we could even add json as well that says error um please add request handler like that so let's say um let's come back here and let's sorry about that let's change this so that it wouldn't match and then we should see that new error because it will hit the fallback request handler perfect please add request handler 4 and then the url that was being called and it raises an exception and all that stuff so it's blocks us from making an actual http request so we're going to do a bit of refactoring now because we want to start testing a component we have this app that ends up calling our convert function but we don't want to have to repeat all of this stuff the before handlers and whatnot every time you want to write a test that calls in http um so what we'll do is we'll create a function or sorry a file called setup server or test server let's say like that and we'll copy over most of the things so we'll cut out sort of i'll actually let's just grab all of this and then remove what we don't need so we don't need the convert function here and what do we want to export to make available let's export the server and let's export rest so that you can say server.use and you can add in additional request handlers like that so coming back to our test here we can now clean out all of this code we can clean out our imports because they're being done already in this file and then we can just import what we need so server and rest from test server so while these tests are running have a sip of coffee and it all still works so to avoid sort of the scenario where you forget to add this import to your file or where you don't want to override any of the defaults what we could actually do is import this same test server file inside of the setup tests file that comes with create react app this is a file that's um run once prior to sort of executing all of the jest tests so we'll just import um from local test server here and now no matter what test you're in it's always going to have a sort of a test server set up and listening for requests to intercept them so with that in place just for fun we could pop this fetch version over to axios and fingers crossed it all still passes and okay i think it's good yeah cool so we'll just keep it on axios for now um going over to writing some tests for our app component first of all let's take a look at what the app component does so what it does it's pretty simple but it sort of sets up two hard-coded currencies from usd to cad puts them in in variables like that and it uses use swr which is a hook from the folks at versailles the makers of zeit that helps you sort of write code that makes um http or asynchronous requests of any kind anything that returns a promise and it does all sorts of things like caching on window if you like switch tabs in your browser when you focus the window again it will remake that request and it uses sort of stale data until it can go to the server and revalidate that request that's where it gets its name from stale while revalidate so what this does is it receives a base and destination these two guys here and it ends up calling this convert function that we've already looked at a couple times so if there's an error we return a span that says error if there's no rate yet so no data we'll assume that we're still loading and if we get past that we have our data so we just display that here so the test for this component um i've already got one set up and i've just xed it out so let me actually run this code so it renders our app component it gives us a function called find by text that we can look for the amount that gives us back an element and we can expect that element to be in the document so if i just tell it to run all tests below so that one's passing why is it passing because inside of the setup test i'd imported our test server our test server intercepted that request and it responded with 1.42 so that's sort of what i'm talking about you get out of the box maybe you add sort of all of your common requests that your app makes into the standard file and then you override it as necessary but let's um tackle testing failure again so it handles errors so this is async there's a couple gotchas not so much with the mock state worker but with the swr that i've run into so what we're going to do is copy this down here except we expect to see error not the typical code and we need to override our mock service worker so let's import server and rest from [Music] uh test server move that up there so it's the same code i had before in the other file so i'm actually just going to copy this over for the sake of time so we've told it to prepend this request handler to respond with a 404 that should trigger an exception which our component will render out in this span tab here so if this were to work um we'd all be really happy we'd end the video now but at least i've been running into problems and it's part of the goodness that um use swr gives you but it caches results so what happened here was it ran the test once and then again and swr sort of cached the the one from here and then when it made the same request here it never even sort of got to our service worker it just used the cash response so we need to basically add a way to tell swr like don't look at your cash just fresh cash every time so let's import a couple things from swr to do that the first thing is swr config will configure there's this thing called a deduping interval to basically say if you see multiple requests that look the same normally it dedupes them but just ignore that and we're also going to access the cache to clear it out after each test so we'll say after each access the swr cache and clear it it's the function to call and we will wrap the swr config around our component that we're rendering oh boy what did i do cool and here we can pass in sort of config values and we'll use the deduping interval and set that to zero so i'm just going to copy this down to here like that so that neither of them now are dealing with deduping and the clash the cache is being cleared every time and it looks like it's passing so let's make it fail just to double check come on you can do it don't give up okay so it actually did render out an error which is what we wanted so let's just make this test pass again and then i think that is it we've covered a number of topics um in this video basically how to avoid low level sort of mocking fetch or axios and instead insert a service worker that can intercept the request so what we did is we set up that service worker and then we gave it a couple sort of default uh request handlers that when they see this request going out it won't go out to the internet it will um sort of capture it and then respond with sort of whatever you want you're basically writing a back-end server server here if you've ever worked with um express where you sort of when this route matches respond with this information sort of thing so we set up some jest hooks the before all to start the server the after all to stop it to close it and then after each to reset the handlers back to the original ones and then we basically just went about implementing that so this one was standard we didn't override it at all this one we pre-pended an additional request handler so that we could basically get our server to respond with a 404 and we tested that it correctly throws that phoral forum and then we went over to the react side of things and we tested our component sort of standard everything's passing and then we did the same sort of failure but the only thing we added was clearing out the swr cache and also adding in the avoid deduping as much as possible so that one test isn't affecting another test one cool thing that is not going to be covered in this video is that msw can be used not only in testing but when you're writing and developing an app you can sort of insert that service worker to have a server respond with fake responses so that even if sort of whoever's doing the back end even if it's you if you haven't done it yet you can insert a fake back end using the mock service worker for those that speak spanish i am going to re re-record this video later today um in spanish it's probably going to be a disaster but i will link to it in the description below so if it's not there yet just come back the next day and it should be there and i'd love to have you on board if you want to subscribe i'm doing javascript and react videos trying for every week hope you're doing well have a great week bye
Info
Channel: Leigh Halliday
Views: 22,029
Rating: 4.9703336 out of 5
Keywords: react, javascript, testing javascript, testing react, service worker, mocking fetch, mocking axios, http in tests, jest tutorial, react testing library, react tutorial, javascript tutorial
Id: v77fjkKQTH0
Channel Id: undefined
Length: 23min 32sec (1412 seconds)
Published: Mon Jun 08 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.