Testing Serverless Functions with Jest & Next.js API Routes

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey team we're gonna learn how we can test our next js serverless functions with jest i'm colby fayok and if this is your first time here make sure you hit subscribe for future updates next.js is a web framework built on top of react but the cool thing is on top of that we can even build api endpoints using serverless functions to do that we use what they call api routes where inside of that route kind of similar to how you would create a new page in xjs we would define a function and define how we want to respond to that invocation now there's already a lot of great ways to test api endpoints including postman but we want a way to actually test the code not just simply how it responds so we're going to use the popular javascript testing framework jest which will allow us to actually run our test on the code where we're going to import our serverless functions and also abstract some of the code to see how we can harden our tests for our serverless functions so to do this we're going to start off with a really basic demo starter that i created just specifically for this walkthrough so we can have an idea of how we can test the code using something that's easy to jump into if you want to follow along with this project feel free to grab the link in the description otherwise it should be able to apply directly to your project so i'm going to get started by scrolling down and i'm going to grab this create next app command which is going to help me scaffold this project so inside my terminal i'm going to simply paste in that command and what it's going to do is it's going to grab this project from github it's going to clone it down to my local directory it's going to install all the dependencies so i have my application ready to get started and once it's ready i can cd into that directory and before we actually get started i'm going to open it up in my code editor so we can have a peek at what's actually inside now if we look inside this project we typically have things like our home page where i even went ahead and i created a demo application where you can actually play around with this in the browser which we'll take a look at in a second but we're not actually going to use the ui at all for this walkthrough instead we're going to take a look at this api route where i created this serverless function where really all it does is it takes a list of items along with a discount and a tax value where it's going to calculate what that subtotal is along with the actual total and it's going to return it inside of the function for the endpoint now like i said we can see what that looks like inside of the browser so i'm going to start up my development server and once it's loaded we can see that we have this pretty simple ui where all i have here is a pre-existing cart a way to add some items and even the order settings so that we can play around with what that actually looks like but if we go ahead and update those values and we actually calculate that order using the existing data we can see that it went out and made that request to the cart api which we can see exactly what it sent by scrolling down which is what we'll use to mock data in a second here but we can also see the preview of the response where it returns that subtotal and that total exactly how we see in the ui so in this walkthrough our goal will be able to write a test for this serverless function without actually having to invoke that api function using a real request so like i mentioned before we're going to use jest to write these tests so i'm going to head over to the getting started page and i'm going to copy the yarn add command head over to my terminal cancel out my development server and i'm going to add that as a dev dependency now back inside my code if i open up my package.json a common way to invoke our test is to write a new npm script called test where we can simply pass in jest along with whatever other configuration options we want with that there we can even see that if we run test it's going to actually call jest but we don't actually have any tests yet for it to find so to start off let's create our first test and what we're going to do is we're going to first create a new directory at the root of our project called tests and inside of that we're going to create another directory called api and while our first test that we're going to write as an example isn't going to be exactly the api we'll update it in a second but this is just going to serve as an example so we can still use the same naming conventions but we want to also then add a new file under api and let's call that cart.test.js so now that we have our test file we want to start our test and to do that we're going to use the test function which is going to take two arguments it's going to first take a description of our test and then it's going to also take a second function as an argument which is where we're going to run our test now for this example let's call this calculates order total but let's say we have an item with a price of five dollars and we also have a constant of quant a d and we have say two items now if we're actually writing this test we want to make sure that whenever we multiply price times quantity which is going to give us our order total that it's going to actually work as expected so we can use the expect function where we can pass in price times quantity where this is going to be what we're evaluating where we can then say we want this to equal where the value of price times quantity and we want that to equal 10. now here we just wrote our first assertion where we say we want to expect that this value is going to equal 10. but now if we run yarn test inside of our terminal we can see that it goes through and it finds that test and we can see that it passed now we ultimately want to protect our code from failing right so what happens if it somehow breaks now let's change this quantity to 3 and let's see what happens if i run yarn test we can see that it again goes through but this time it fails because it says that it expected it to be 10 which is what we expected it to be but they actually received 5 15 which is because we change the quantity to three but rest assured when we change that back to two and we run that test again we can see that because it's what it expects it to be it's going to pass but now that we have a basic understanding of how the test function works let's actually apply this to our serverless function so if we look back inside of our api route for our serverless function we can see that we're exporting this function called handler so that means just like any other function that we would export from a file we can import that directly into our test so at the top of my file i'm going to start off start off by writing import cart from and i now need to navigate up to that tree to find that api route so i'll say up one up two and then pages api and then cart if i now get rid of all this code inside of my test function and i console log out cart just to see what happens and when we run yarn test we can see that it goes through and it actually fails now the issue is if we see inside of these details it's because we're using an import statement now by default jess can't actually understand this import statement what it would typically see is a constant cart equals require along with that path but i like to use the import statement syntax since it kind of flows a little bit more with the rest of my project so to get around this we're going to actually use a babble plugin where we're gonna say that we want it to transform our code so that it can actually understand those modules so to start i'm going to add that as a dependency so i'm going to say yarn add that package minus d so that it saves it as a dev dependency but now in my project i need to create a babel rc which is a battle configuration so in the root of my project i'm going to create dot babble rc now when customizing babble inside of an xjs project we need to make sure that we cover the default settings from xjs where we can still provide any overrides that we want but we got to make sure that this next babble package is still included so that next js works as expected so i'm going to go ahead and copy this example where we're going to set a preset of next babel along with an empty array of plugins i'm going to go ahead and paste that right inside of my babelrc but now instead of actually adding this inside of the plugins array we want to only apply this to the test environment so we're going to define a new property called env for environment and inside of that we're going to define another property called test and finally we're going to recreate that plugins array so that we can pass in our new plugin now when we run yarn test we can see that it goes through finds the test and this time it actually runs and it passes because we don't actually have anything inside there but we can see that it found that handler function so in order to actually test this cart function we need to invoke it so that means we need to run it where we're going to pass in any arguments that it requires if we look back inside the function we can see that it's taking two arguments a request and a response so i'm going to start off by creating my request object and i'm going to currently just keep that as an empty object for now along with a response object so that we can go ahead and pass these right into our function as arguments now to actually fill out those objects we want to look and see what we're actually passing in now for the request object we can highlight this and look through and we can see that we're only passing it in using the body property which we're actually parsing because it gets passed in to the endpoint as a string when we're actually making real network requests and we're destructuring our discount tax and items from that body so we're going to ultimately want to create that body property where we're going to create a new object where we want to pass in our discount our tax along with our items i'm going to go ahead and paste these values in but we can see that i'm sending in a discount of 0.2 a tax of 0.06 a common sales tax here in the u.s along with an array of items which we can see each of those objects inside of the items array have three properties it has an id it has a price and it has the quantity that way it has enough information to actually go through and calculate the subtotal and the total for our order now finally if we remember we're passing that body in as a string because that's just simply how the serverless function endpoint works so we want to make sure we stringify that data before sending it through so on our body we're going to run jason stringify and we can just pass that in as a string so now that we have our request object we want to actually create our response object now this one's going to be a little bit more tricky if we take a look and see what's getting used we can see that it's only being used to create our status code along with passing back a response of json now typically when you're writing a test against a function you want to capture the output that's returned and use that for your test assertion but we're not actually returning any data from this function instead it's using a callback in order to resolve the request now to make this work we're going to use mock functions which is a way to create a function using jest where after it's called we have the ability to actually inspect that function call to see both when it was called how many times it was called and also the arguments that it was called with so that means if we create a mock of both the status and the json we can see when this json was called and what data was actually passed back now typically we would write it the same way we wrote this request up here where we would just write everything inside of a single object like status and then inside of that right json where we would have our function but because of the way that the jest mocks work we're not going to be able to actually access that json property from inside of that where we're going to have to use that in order to inspect it later inside of the test so instead i'm going to first create my json constant which is going to be a just mock which i can define as jest.fn which stands for function then i'm going to define my status which is going to also be a jest function but inside of that we're going to actually pass back what we want to return from that function and inside of here we want to return an object with our json so that when it chains that request at the end of the callback it's going to be able to find that json function and pass back the data exactly how we expect it to but then we can pass in our status inside of this response object and we can now see that we have all of our data actually fulfilled now if we head over to our terminal and actually try to run the test we can see that it goes through and it runs the test but nothing's actually happening yet and it's because we haven't wrote an assertion we're not logging anything and nothing's really going on that we can actually see so to start i mentioned that once we have this json and the status function using just mocks we can actually inspect that so let's see what happens inside of the json so after i invoke cart here i'm going to go ahead and use console.log where i'm going to say i want to log out jason but i'm going to log out mock now you can go ahead and inspect what happens inside the json object itself but it's a lot of stuff that we don't necessarily need right now so let's look directly at the mock object if we run that we can see that it's going to go through like usual but this time we can see our console log and we see that we get all this information about our invocation where we can see that it has this instance that was invoked where it actually returns that json now the thing that we're going to be interested here is the calls where that call is going to be where it was invoked and what it was invoked with so i'm going to go one level deep and i'm going to specify that i want to console log out the calls and if we run that we can see again it goes through but this time we can see exactly the data that we passed in when we were trying to run that test so now our goal we know that this is a correct value because our code is currently working as expected so we want to make sure it always works as expected so we're going to grab this value in order to write our assertion which we can see that it's two arrays deep and then it's going to be the subtotal property or the total property we're gonna write the subtotal for now on that object so i'm gonna say i want to expect that my json.mock.calls and again 0 0 to access those arrays which if we have multiple calls we might see multiple array objects there but then we want to access that subtotal where we want to grab that dollar value and we want to say that we want to expect it to equal that amount and before we run the test i'm going to get rid of this console log and now when we run the test we can see it goes through and this time it passed and this time it actually passed by using this expect statement now to prove that this is working we can see exactly what happens if we actually break this serverless function since we're testing the subtotal what happens if i remove the quantity from the statement where all it's going to do is for each of the items that are in there it's only going to calculate the subtotal plus the price of one item which of course is wrong but now if i run my test it's going to go through and we can see that it actually fails because it doesn't expect the right value now this might seem like a simple example and it is because it's intended to be but this becomes a lot more complicated when you have a lot of business logic revolving around a single serverless function or really any kind of code but of course like before if we remove that actual error and we run our test again we can see that it's going to go through and pass so this is great we completed our goal of being able to test a serverless function and we did that by being able to use the jest function mock where we were able to expect what was inside being invoked for that actual service function but inside of our serverless function we can already see that there's a lot of stuff happening inside here so what i like to typically do is abstract my code so that i can have little chunks of logic that i know are working as expected so that we can rate test exactly for that little chunk of code so to see how this works let's abstract the subtotal calculation so that we can write a test just for that subtotal so to create this abstraction i'm going to create a new folder in the root of my project i'm going to call it lib and inside of that i'm going to create a new file called orders.js and inside of this i'm going to export a new function and let's call that calculate subtotal from items where inside of this function we're going to ultimately return the value but we're going to take an argument of items which is going to be an array of our items but back inside of our cart code let's grab this reduce statement and we're going to simply return that so when we run this function with our argument it's going to do the exact same thing as it was doing inside of the serverless function but it's going to do it in a way where we're simply invoking that function where we're containing that code in one chunk now to make sure this works let's grab our function and head back over to our serverless function where let's import that function from we're gonna go two levels up to the lib directory and go to our orders file we're then going to replace this entire statement where we're going to say we want to call that function and pass in our items which is going to return the value for our subtotal now to make sure this still works let's run yarn test and when it goes through we can see that it still passes so we're good and when we run yarn test we can see that it goes through like typical and we can see that it passes so we're good to go with that abstraction now when i run this test as is i don't have any information about where it failed if it actually fails so i want to know exactly what's happening so what i'm going to do is write a test specifically for this function to do that back inside of my test directory i'm going to create a new folder called lib inside of that and just to be clear you can really organize this however you want i like to try to mimic my actual project directory so that i have a little bit more of a clear understanding of where things are located but inside of there i'm going to create a new file called orders.test.js where we're going to do the exact same thing and run a test for that function so just like how we used it inside of the serverless function i'm going to first import our function but then we're going to start setting up that test where again we have our string which is our description of the test along with the function where we're going to actually write the assertion so for this one let's say returns calculated subtotal from items now we want to have some mock data for those items so back over in our serverless function test i'm going to simply copy this data exactly like we were using before i'm going to say constant items is equal to that array and now when you're writing tests it's good to vary the data that you're passing in just to make sure you don't have any issues where it's working every time for a specific way but if you vary your data you know that you're providing more of a variable like in a real world so that you can make sure it's working in all cases but now we're gonna grab that function and i'm going to invoke it by passing in items and this time we're going to say that we want to expect that that value is 2 equal and because from our other test we already know that this is going to be equal to 83.47 i'm going to be able to pass that in as my two equal statement but now again when i run yarn test it's going to go through but this time it's going to find two tests and we can see that they both pass now we can again see how this works if this fails and let's do the exact same thing where i'm going to remove the quantity where it's only going to add the price of one for each item if i run this test we can see when it goes through that both tests actually fail which is what we were expecting we can see that our api test fails because it's returning the wrong value when it's returning the order in the subtotal but we can see also that it's failing when we're running that specific function meaning when we see that that api fails we know that right now we're able to pinpoint it to that specific function because that's failing as well this way of abstracting and writing tests can work for all these different parts where we're calculating our discount or we're calculating the tax as these are all important things when we're dealing with money to make sure that our business logic is always working as expected but again once we resolve that bug and we run our test again we can see that it goes through and it passes and we're good to go testing is a critical part of development where we're making sure that our code is always working as it expects it to be using jest we have the ability to test our serverless functions our abstracted utility functions as well as a lot of other things that we didn't cover today if you want to learn more about how to level up your testing game you can check out my video where i walk you through how to use postman to actually test the requests as well as how you can use tools like apple tools to use visual testing to provide really broad coverage for your application as well as how you can automate all of this inside of your project with github actions what's your favorite type of test or what's your favorite library for writing tests let me know in the comments otherwise if you like this video make sure you give a thumbs up and subscribe for future updates thanks for watching
Info
Channel: Colby Fayock
Views: 6,416
Rating: undefined out of 5
Keywords: jest, jest tutorial, jest testing, jest mock, jest unit testing react, jest serverless, jest function testing, jest function mock, jest function mock example, jest function inside function, jest function calls another function, jest function, jest mock function tutorial, jest mock function, jest mock implementation, jest nextjs, jest next js api, test next js api, next js testing, next js testing jest, next js test api, next js test api route, testing serverless with jest
Id: 2HFEFz7LWdE
Channel Id: undefined
Length: 20min 29sec (1229 seconds)
Published: Fri Aug 20 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.