How to use TDD to refactor a web front that consumes a REST service

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
in this video we're going to use test-driven development to refactor a web front-end that consumes a REST API [Music] hi I'm Bill Sewer from dev mastery and you're watching mastery Monday the weekly show that helps you improve your code and advance your career don't forget to subscribe and hit the notification bell so you never miss an episode in a previous video we built a web app that let us search for movie posters behind the scenes our app is consuming a free REST API from the open movie database we use test-driven development with Google puppeteer and just we followed an approach of first make it work then make it better the good news is that led us to get to working code very quickly the bad news is the code is not that pretty but that's okay because having working code is extremely valuable it allows you to confront any potential gotchas along the way and it gives you a fallback position if a deadline is looming once you've got working code you can go back and refactor and make it better and that's what we're gonna do in this video here's our code on the left hand side we've got the tests on the right hand side we've got the code that satisfies the test and on the bottom we've got our test runner running continuously to make sure that we haven't broken anything the first thing that jumps out at me is something that's probably worth changing is this hard-coded business rule right here so you see when we type into our text box we want to make sure that we've typed at least three characters before we enable the search button and we do that with this little function here but we're hard coding that number three this rule probably doesn't belong in here it probably belongs at a higher level or somewhere else inside of our component because by putting it in here the only way we can access it is through a sort of a react construct so at a minimum I think it's probably worth setting that rule on the text box itself so we're gonna go ahead and do that here's our text box and then here we can just add a min length now we can go back up to our handler grab the min length and use it instead of the hard-coded value and our test still passes I just realized another problem we've got some text up here enter at least three letters from the movies title and again this number three is hard-coded it would be nice to be able to reuse the same value in all places so I'm gonna go ahead and define a constant for minimum movie length and now we can use that constant in our message and we can also use it to set the min length of the textbox let's save and make sure we haven't broken anything awesome still get the next opportunity for refactoring that I see is in this button handler right here this click Handler so what's going on here is we're calling our API directly from the click handle and that's fine if there's only one component that uses this API but if we imagine that our app has started to expand and that we're using the API all over the place it's a bit dangerous to be too reliant on knowing the details of the actual API from within all of our components especially because this is a third party API that we don't control so when something changes in the API we don't want to have to go back and change all of our components or touch every place within our app ideally we could isolate the details about the API into one place and then have our components consume that so we're going to create what's called a seam a seam creates some separation between different components within your application to do this we're gonna step away from our integration tests and move to more traditional unit testing inside of our poster search folder I've created a brand new module called find movies and I've also created a spec find movies dot spec j/s because we're gonna use test-driven development to build it so with fine movies our goal is to create a function that given a movie title will return a list of movies the advantage of having this kind of function is that we can hide away all of the details about working with our API we just take in a movie title do all the work of creating an API call behind the scenes issuing that request to the server getting back the results and then when we returned the results to our caller we can actually normalize those results so that the shape of our movie objects conforms to what our app sees as a movie rather than simply using the representation that's given to us by the server and the advantage of that is that when things change on the server side because we don't control the API we've isolated ourselves from the changes the only place where we'll have to change our code is inside of this module the rest of our app that depends on this code will not have to change at all so the tricky bit of course is we're going to have to make a network call inside of this function and that can be tough to test because Network calls are typically slow and especially in this case where we're actually making a call to an API that we don't own or control all kinds of unpredictable and unexpected things can happen with that API so we want to take more control over what's happening one of the ways to do that is to mock out whatever library we're using to make the API call in fact that's what you'll see in the jest Docs so here's the documentation from the jest website they give this example where they're creating a user by calling an API with fetch and then to test that code that creates a user they've mocked out their fetch library and so their test uses the mock in place of the actual fetch library so that they have control over what gets sent and returned and this works but to be honest with you I don't love it for a couple of reasons one it sort of encourages you to write tests that are not that useful because the test is simply asserting that your mock works the way it's supposed to work but you don't want to test your mock you want to test your actual code so if you look closely here you'll see they're mocking the return value and then they're asserting the return value is the value they mocked in so that's not very useful you're just asserting something about the mock you just created so this test isn't really testing their code the other thing is if you look at this code very carefully there are actually a couple of different things going on now this is super simplified but what's happening here is they're constructing a query that needs to get sent to the API and they want to test that they've constructed that query properly so that's what this assertion is about down here so that's one test the other thing that they're doing is they're sending back a response and they want to make sure that the response that's coming back is conforming to what they expect except as we saw they're not really testing that because the response is coming from the mock the other thing is they're just sending the response as is they're grabbing the text from the actual network response and sending that back but in our case what we want to do is we want to control the response we want to change the representation of our movie objects to conform to what our app views as a movie rather than simply taking for granted that the API is going to return us a consistent representation every time we want to isolate ourselves from changes in the API so if you think about it you can actually divide this into three steps so the first step is create the query the second step is issue the query and the third step is format the response and here they're not doing any of the formatting but in our case we will format so what we're saying is step one build a query based on the user's input step two issue the query and step three format the result if we do that then potentially we can isolate each of these steps into their own functions which means we can test this function independently and this function independently and we can actually skip testing this piece because it's not going to do anything it's just simply going to call out to our library that does the network request and we already have an integration test that we wrote with puppeteer that makes sure that all of that is gonna work that might sound confusing at this point but just stick with me and you'll see how it works so for our spec we're gonna describe find movies and we're gonna write a test for building a movie query and then we're gonna write another test for normalizing the results of a movie query so we'll ignore the second one for now by placing an X in front and we'll start by building our first test it builds a movie query so a movie query is basically going to be an object that we pass to our API that does the HTTP request and that contains the parameters that our API our om db-api expects so in our original app we were using fetch but I think I'm gonna switch to Axios Axios is an extremely popular HTTP client for javascript and i like using it a lot more than I like fetch fetch will do in a pinch but if I'm gonna pick an API library to use it's going to be or an HTTP request library to use it's usually gonna be Axios the other reason that I'm doing this is to sort of show off because you'll see in the end our puppeteer tests will continue to work even though we switched from French to Axios so Axios expects an object that has a method a URL and in this case it's passing along data but for us because we're using query strings we're gonna pass along params in other words our test is going to expect something like this and we'll set a constant for our movie title and so we'll want to assert that calling build movie query with a movie title of Batman Returns that expected object and if we save this we should have a failing test so of course build movie query is not defined so we'll need to write that function over in our find movies module and then we'll need to import it in our spec now if we say we should still have a failing test okay and this fails because we're receiving undefined of course since our function isn't returning anything and we're expecting this object so let's implement our function and save and there we go a passing test so let's move on to it normalizes query results so for this I'm just going to create a dummy API response that looks like the real thing and if that's our input think I'm gonna want something back that looks something like this you and so we'll expect normalize dummy results or dummy posters to equal expected so let's watch this fail gate we need to export a function called normalize move your results save okay now we're failing because we're receiving undefined so let's implement you okay let's save and voila passing so now we need to modify this to handle the case where the poster is missing so our API returns an n/a when the poster is missing but I think we can do something a little more elegant so we'll just expect a missing poster to just be null and we'll save this and watch it fail and then we'll implement and we're passing again so next we'll handle the case where there are no results and so zero results from our API looks like this and we want to translate that to something like this and so we can assert that are normalized movie results for zero results returns that expected result okay let's save and watch it fail and so of course our results a search property so it's failing on that no problem let's jump in and implement and we'll save great passing tests next we'll handle other API errors so an API error looks like this and we'll just expect an object that just contains the error let's watch it fail and we'll implement we save and back to green okay so now we need a function that ties it all together and before we implement this we'll wire it up to our component and watch our integration test fail so back in our UI component we can import our fine movies and in our click handler will remove fetch and replace it with fine movies and now our integration test should fail and sure enough everything is broken so let's go and start to implement we'll need to import Axios and we'll build our query pass it along to Axios catch any errors we'll just console.log for now and we'll throw a generic error so our testers because we're not consuming the new shape of our response so let's fix that you and we'll save still not working because we're depending on the original I shape down here save again right because we're passing in search still so this needs to be movies now we'll save and everything works so we can check the app for ourselves just to make sure looking good so of course there's always more refactoring we can do for example this fine movies function is now a pretty good candidate for what's called pipeline style programming because all it is is your piping the result of one function into the next into the next I'll put a link in the description to my video on pipeline style programming and maybe you can download the code yourself and see if you want to try it that's it for this week see you next Monday [Music]
Info
Channel: Dev Mastery
Views: 3,586
Rating: undefined out of 5
Keywords: test driven development, code refactoring (software genre), web development, test-driven development, test driven development javascript, test-driven development (tdd), unit testing, devmastery, dev mastery, Bill Sourour, Jest, JavaScript, REST, React, reactjs, javascript project, javascript tutorial, web development projects, web development tutorial, javascript tdd
Id: IOCcqIKJyFk
Channel Id: undefined
Length: 17min 36sec (1056 seconds)
Published: Wed Feb 27 2019
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.