A Guide To API Service Layer Testing With The Jest Framework
Not all of our testing for our React app relies so heavily on RTL — some files Jest can handle on its own.
This lesson preview is part of the The newline Guide to Modernizing an Enterprise React App course and can be unlocked immediately with a \newline Pro subscription or a single-time purchase. Already have access to this course? Log in here.
Get unlimited access to The newline Guide to Modernizing an Enterprise React App, plus 70+ \newline books, guides and courses with the \newline Pro subscription.

[00:00 - 00:12] As you can see by examining the structure of our app, I like to keep the API calls in a services folder separate from the components relying on them to supply data. The reasons for this are twofold.
[00:13 - 00:28] The first is to organize and centralize API calls into one place in our app. No matter what component needs to make an API call or if a developer on the team needs to add a new one, they'll know exactly where to go to find and extend all these calls.
[00:29 - 00:45] At the same time that they're all grouped together in a folder, I also like to keep the calls in separate files grouped together based on the API routes that they're using to access data. For example, all API calls related to our checkout are together in one service file.
[00:46 - 01:00] If we had a user service, all the calls related to that would be in a separate file inside of our services folder. The second reason is that decoupling our API calls from particular components within the app makes everything easier to test.
[01:01 - 01:19] This separation of concerns, components versus the implementation to get the data, makes integration testing and refactoring for that matter easier and more straightforward. In this lesson, we'll learn how to test JavaScript files that don't directly interact with the DOM and the React components.
[01:20 - 01:36] Step aside, React testing library, it's time for just to shine. For consistency's sake, we'll set up the tests for our service calls, similar to how we set up testing for our custom hooks in a test folder nested inside of our services folder.
[01:37 - 01:47] This way we keep the precedent that we've been setting for our app. Tests not tied to a particular React component or container get grouped together inside of a test folder for easy discovery and access.
[01:48 - 02:05] In our IDE, let's go to the services folder inside of our clients source folder and create a new folder that is named test. Now that our test folder is in place for the service layer of our React app, we can start writing tests for our various services.
[02:06 - 02:22] As mentioned earlier, the way that I like to divide up service files is based on related API call routes, hence the three services in our app for departments, products and checkout. To start us off easy with API testing, let's tackle our simplest API, the departmentapi.js file.
[02:23 - 02:36] Inside our new test folder, create a new file named departmentapi.test.js. Our new test file is going to need a couple of imports before we can get started with writing the tests.
[02:37 - 02:49] When we look at the actual service files, we can see that the Axios library is used to make the API calls easier. So if I split this open, you can see right here we've got Axios.
[02:50 - 03:04] Axios provides a whole host of additional benefits that the browser's native fetch API does not, but that's beside the point of this lesson. One thing that Axios does not make very easy on its own is mocking requests and responses for integration testing purposes.
[03:05 - 03:16] To simplify this process, we're going to add another NPM library named Axios mock adapter. This library makes mocking Axios requests easy, and I'm excited to show you how we can make it work for us.
[03:17 - 03:36] So the first thing that we'll need to do is save the Axios mock adapter library to our project's dev dependencies. So inside of your client folder in the terminal, go ahead and write yarn, add Axios-mock-adaptor-dev.
[03:37 - 03:48] Now that we've downloaded Axios mock adapter, let's double check our package JSON file has the library. It should be right after our testing libraries in the dev dependencies.
[03:49 - 03:56] Let's open that up. And if we scroll down to here, we see that Axios mock adapter is present.
[03:57 - 04:01] Great. Once that's confirmed, we'll add the following imports to our departmentapi.
[04:02 - 04:14] test.js file. We're going to add Axios, Axios mock adapter, and the department API, of course . So first thing we will import is Axios.
[04:15 - 04:33] Next, we will import the mock adapter from Axios mock adapter. And finally, we will import star as departmentapi from our actual departmentapi .
[04:34 - 04:41] We've got access to Axios and our mock library now in our test file, so it's time to set up the mock adapter. We'll make our mock call simple.
[04:42 - 04:54] We will make a describe block, which we will call describe testing department api service. Pretty self-explanatory.
[04:55 - 05:00] Arrow function. And right inside of that, declare a variable called mock.
[05:01 - 05:07] We will have const mock. Actually, we will turn this into a let mock.
[05:08 - 05:26] And inside of a before each, we'll instantiate a default instance. Then any tests that call this instance inside of our tests will access this object instead of trying to call the real Axios API, which might not have access to the right data to successfully complete the API call every time the test runs.
[05:27 - 05:35] So we will create a before each. And inside of that, we will have a mock, which is equal to our new mock adapter .
[05:36 - 05:38] And we will wrap Axios in it. Very good.
[05:39 - 05:48] We're ready to test now. So in the actual departmentapi service, the get all departments call is designed to return all the departments present in our app.
[05:49 - 05:58] It just so happens that we had this data mocked in our mock data set dot JSON file. So we can use that as our mocked data API call should return.
[05:59 - 06:13] So first, we need to import the mocked data into our test file. So right above our departmentapi import, we are going to import in data set from slash slash underscore underscore mock data.
[06:14 - 06:23] Mock data set. And then write a new test that if the Axios call returns department data successfully, this mocked department data is the data that it will return.
[06:24 - 06:39] So when the slash department's endpoint is called by the mock, we'll tell it to reply with a 200 HTTP response and the mock all departments variable that we defined on the line above. So our first test is going to look something like this.
[06:40 - 06:53] We will say it should return all departments. When the get all departments end point is called.
[06:54 - 07:03] This is not going to be an async function. And then once we're in here, we will have mock all departments as a variable.
[07:04 - 07:13] And we will set that equal to data set dot departments. And then we will call mock on get.
[07:14 - 07:25] And then the slash departments endpoint. And we will tell it to reply with a 200 status call and mock all departments.
[07:26 - 07:36] Excuse me, this is an asynchronous call. And finally, we will call the department API get all departments function and check the response that it gives back.
[07:37 - 07:53] The variable that we define as actual departments is the same as our mock all departments variable. So we will have const actual departments, which is equal to await department API dot get all departments.
[07:54 - 08:04] And we can expect that our actual departments dot to equal our mock all departments. Not so bad, right?
[08:05 - 08:20] Take note that all of these tests are going to contain async await because they 're all making API calls, which are always asynchronous. Just like when we've tested our other components and hooks, we want to integration test to get all departments call when it succeeds in fetching data and when it fails.
[08:21 - 08:32] So we need to add a second test of this file when the latter scenario occurs and the department data can't be fetched. For this API, when department data is unavailable, an error message gets returned instead.
[08:33 - 08:37] So let's write a new test that describes this purpose. We will say it.
[08:38 - 08:43] It would return an error message. When the.
[08:44 - 08:48] Yet all departments. All fails.
[08:49 - 08:59] We will make this an async function. And the first thing that we will do is where we previously mocked department data will mock the fetch department data error constant instead.
[09:00 - 09:09] Don't forget to import at the top of this file. And when the endpoint is called this time, that error will be returned along with a 500 HTTP response.
[09:10 - 09:20] So we will have const mock error message is equal to the fetch department data error. We will mock our on get this time.
[09:21 - 09:30] And when departments is called. It should reply with a 500 and the fetch department data error.
[09:31 - 09:46] Finally, call the get all departments function once more and compare the error messages. So we will have const actual error message and await the department API dot get all departments.
[09:47 - 09:53] And then we will expect our actual message. To equal.
[09:54 - 10:03] Our mock error message. And let's go back over and check our tests, which should be running for us in the file watcher.
[10:04 - 10:09] And it looks like we now have 15 tests passing. But if we scroll up.
[10:10 - 10:14] You might see some red right here in this console. You don't need to fear that.
[10:15 - 10:20] When you run the second test, you may notice a giant red error in the console. It's really nothing to worry about.
[10:21 - 10:27] It occurs because there is a console error in the actual API call. Right here.
[10:28 - 10:39] And that is printing out to the terminal what happened when the call failed. If you comment out the console error line in the API and run the test once more , the error message that appears during the test should disappear.
[10:40 - 10:45] Either way, it's nothing to be concerned about. Just a warning message, the test passes, which is what we care about the most.
[10:46 - 10:53] I'm just going to keep moving on. Do you see now how straightforward the Axios mock adapter library can make our testing efforts?
[10:54 - 11:08] As long as we mock the same route, our named API call uses, we can define exactly what it should return every time. Not only that, but with the Axios mock adapter, we can do things like chain multiple API calls together with different responses.
[11:09 - 11:20] We can define particular network errors as the desired response, use regX to match API routes, and more. I highly encourage you to check out the documentation to see just how full featured it is.
[11:21 - 11:31] If we run our code coverage now and check the department API.js files code coverage in the browser, you should be pretty pleased with the results. Let's give it a shot.
[11:32 - 11:43] If we run yarn coverage and then we scroll up, department API is at 100% code coverage. That's hard to beat.
[11:44 - 11:49] Okay, let's tackle another service layer file. This one will be a little more challenging than the last.
[11:50 - 11:56] This time, we'll target the product API.js file. So let's open back up our sidebar.
[11:57 - 12:06] We will create a new file inside of our tests folder. This one will be named product API.test.js.
[12:07 - 12:24] Our second set of service level tests should feel more familiar after all the other integration tests that we've written up to this point. Let me bring that over here and we will open up the product API over here to be able to keep an eye on it as we're working.
[12:25 - 12:38] So the first thing that we will do is the same imports that we did for the department API. So I'm going to just borrow those from up here for Axios and our mock adapter.
[12:39 - 12:56] And then of course for this one, we will also need to import as a wild card our product API. At this point, we can write our describe block to wrap all of our tests and set up our local mock object that the tests can rely on for their API calls.
[12:57 - 13:07] Recall how to do this from earlier in the lesson. Our first couple of tests should be quite similar to the tests that we just wrote for the department API.js file if you want to reference that as a guide now.
[13:08 - 13:27] So we will set up our describe block and we will be testing the product API service. We will have a new mock and then a before each where we will attach that mock to the new Axios mock adapter.
[13:28 - 13:44] The mock is equal to new mock adapter and then wrap Axios in it. With our mock created, we can write our first test after checking the API calls present in our product API.js file.
[13:45 - 13:54] The first call that we get for there is get all products. So when that endpoint is called, we'll use the mock data from our mock data set file to return product data.
[13:55 - 14:05] So let's import that mock data again. Mock data from mock data, mock data set.json.
[14:06 - 14:21] And we will set up the test and our mock inside of our describe block. Just like with our tests for the get all departments API call, we just need to set the mock all products variable equal to the array of mock product data in our mock data set.json file.
[14:22 - 14:30] When the route is called, the mock will return that array of data. So our first it test is going to go a little something like this.
[14:31 - 14:42] It should return all products when get all products is called. This is an async call.
[14:43 - 14:55] And we will have const mock all products, which we will set equal to our mock data dot products. Then we will mock our on get.
[14:56 - 15:12] And when products is called as the end point, we will reply with a 200 and mock all products. In the end, we'll finally have the test call the API function and check that its result is the same as our mock result.
[15:13 - 15:38] So from here, we will have const actual all products, which is equal to await and then product API and get all products. And then we will check that they are equal to the same thing by checking that actual all products to equal our mock all products.
[15:39 - 15:46] And I will switch back over so that we can watch all of our tests passing, which they are, which is great. Perfect.
[15:47 - 15:54] Just like how we tested the other file, we'll need to test this API call when an error is thrown from the server. It's attempting to retrieve data from.
[15:55 - 16:02] If you'd like to, now would be a good time to try writing this test on your own . It's very similar to the failing test that we wrote for our department API.
[16:03 - 16:10] Go ahead and pause this lesson if you want to take a shot at it yourself. Here's how I would approach this test.
[16:11 - 16:17] Set up the test and mock the error that is returned when it fails. Import this error constant into your test file.
[16:18 - 16:29] So my test would look something along the lines of it should return error message. When the get all products all fails.
[16:30 - 16:43] This will be in asynchronous function. And then I will create a const mock error message, which is equal to the fetch product data error.
[16:44 - 17:02] And then for this mock on get for products, we are going to reply with a 500 and the mock error message. And after the setup, call the API and confirm that the responses match.
[17:03 - 17:12] So we will have an actual error message right here. And we will await the product API, get all products.
[17:13 - 17:31] And finally expect that the actual error message dot to equal the mock error message. The second set of tests that we write for the post that adds a new product to our list will require a slightly different setup to test properly.
[17:32 - 17:35] This add new products. We'll go through this one step by step though.
[17:36 - 17:54] Our first step will be to write a test wrapper statement. It will be something along the lines of it should add a new product to the list of products with valid data entered.
[17:55 - 17:59] Something along those lines. Async for this error function.
[18:00 - 18:09] And looking at what this add new product API call takes in, it requires a product to add argument in order to work. So next we'll mock that new product data.
[18:10 - 18:21] We don't already have an existing mock for this data in our JSON file, so we'll need to define it here. There's no validation checking like department number or if the product already exists in the database or anything like that.
[18:22 - 18:30] So pretty much any data we mock here should work. The data I mocked just so happens to align with the mock departments and some of the existing products in our JSON file.
[18:31 - 18:36] But it doesn't have to. I would recommend that you just copy this out of the lesson itself for time's sake.
[18:37 - 18:46] That's what I'm going to do. So the first thing inside of this test is to find this new mocked data, which I copied from our lesson.
[18:47 - 19:01] So we have new product data right here. And once that mock is defined, we'll make our mocked post call where we pass the mock new product data object that we just instantiated into our mock on post method right after the product route is declared.
[19:02 - 19:25] So our reply method remains the same as all the others that we've written an HTTP call and a success message constant that will import at the top of this file. Our mock should look something like this mock.onpost and we use the products route and inside of here mock new product data.
[19:26 - 19:36] That's how we pass it. And then in the reply, we will have 200 and the add new product success message .
[19:37 - 19:59] And then we'll call the actual add new product endpoint while passing our mocked new product and check that it returns the same success message as the response. So outside of our mock, we will have launched actual result, which equals await product API dot add new product.
[20:00 - 20:16] And we will pass in our mock new product data. And then we will expect that our actual result is equal to the add new product success and save that following along still.
[20:17 - 20:21] Great. So if the new product cannot be added, let's test that as well.
[20:22 - 20:33] What happens when adding a new product to the list fails? Since it doesn't really matter this time, what our product to add argument is when it's passed to the mocked function, we won't bother to make a mocked item for this test.
[20:34 - 20:45] Before this test, we'll just write the test and the mocked post failure right off of the bat. So for this last test, we will have it should return an error message.
[20:46 - 21:04] If a product fails to be added to the products list, this will be in asynchronous call because they all are. And we will do a mock on post for our products end point.
[21:05 - 21:13] And we will pass it an empty object for now. And then the reply will be a 500 and an add new product error.
[21:14 - 21:28] The failing test should return a 500 HTTP call and the add new product error constant, which should also be imported at the top of the file. With this mock in place, we can now call the actual function and compare the results to our mocked error.
[21:29 - 21:43] So we will have const actual error message, which we will set equal to the product API dot add new product. And we'll pass it in an empty object.
[21:44 - 22:01] And then we will expect our actual error message dot to equal our add new product error. Upon completing this test, we should have covered all scenarios for all our API calls in the product API dot JS file.
[22:02 - 22:09] Nice. Okay, let's run all of our tests and check our code coverage after testing these API files and see where we're at now.
[22:10 - 22:24] Looks like they're all passing, so if we head over here and run our coverage report one more time. Now, both Department API, JS and product API, JS are at 100% test coverage.
[22:25 - 22:27] Sweet. That looks great.
[22:28 - 22:44] I hope after writing tests for two of our three API services, you feel more confident about how to use just to test our non-react-centric pieces of our app . If you'd like, you can take a crack at testing the checkout API dot JS file to help solidify what we've just covered.
[22:45 - 22:50] It would be a good challenge to test your understanding. Otherwise, I'd say that we're done with this lesson and with this module.
[22:51 - 22:56] Great job. Let's recap what we've learned over the last five lessons about integration testing.
[22:57 - 23:02] Then we'll move on to more testing, this time of the bigger picture, the end-to -end variety.