Devot Logo
Devot Logo
Arrow leftBack to blogs

A Comprehensive Guide to Testing API Calls with Jest

Max L.9 min readDec 12, 2024Technology
Max L.9 min read
Contents:
What is Jest?
Installing and setting up Jest
Tips for keeping your tests clean and maintainable
Making API calls in JavaScript
Mocking API calls with Jest
Writing unit tests for GET requests
Testing POST requests with Jest
Testing PUT requests with Jest
Testing PATCH requests with Jest
Testing DELETE requests with Jest
How to handle asynchronous code in Jest
Error handling in API tests
Advanced Jest features for API testing
Final words on JEST API testing

APIs are the lifeblood of modern applications, enabling communication between servers and clients. Whether you’re building web apps, mobile apps, or microservices, testing API interactions is critical. In this blog, we’ll explore how to test API calls using Jest, a powerful JavaScript testing framework.

We’ll focus on:

  • Mocking API calls,

  • Using beforeEach and afterEach for test setup and removal,

  • Testing API responses and error handling,

  • Best practices for writing efficient and maintainable tests.

This blog is ideal for developers who use JavaScript and JavaScript frameworks and want to write high-quality tests for their API interactions.

What is Jest?

Before diving into API testing, let’s talk about Jest. If you’re new to it, Jest is a versatile testing library known for its simplicity and suitability for all kinds of JavaScript projects. It offers a rich set of features, including:

  • Assertions to check conditions (e.g., expect).

  • Mocking to simulate API calls, functions, or modules.

  • Spying to monitor function calls and behaviors.

  • Snapshot testing for UI consistency.

In addition to these features, Jest stands out for API testing due to its:

  • Simple and minimal setup.

  • Built-in mocking capabilities.

  • Excellent support for testing asynchronous code, which is common in API interactions.

With Jest, you have everything you need to test API calls effectively and without unnecessary complexity.

Installing and setting up Jest

Before installing and setting up Jest, make sure you have Node.js installed.

Step 1: Install Jest

The --save-dev command ensures Jest is saved as a development dependency, meaning it will only be used for testing during development to make sure everything works as intended.

Step 2: Set up Jest in package.json

After the installation is complete, add the following test script to your package.json file:

TIP: If you want to see which parts of the code are being tested, add the --coverage property:

This will now actually track every single line and function that is executed during testing. It generates a coverage folder containing an index.html file. When you open this index.html file in your browser, it provides a visual representation of the code being covered and tested. All parts that are not covered and tested are marked with a red background.

With this, you can make sure your code is 100% covered and tested.

Step 3: Run your tests

Run your Jest tests using the following command:

TIP: If you want to avoid testing multiple files at the same time, you can test one specific file with its test cases by adding its name to the command mentioned above:

API testing with Jest

Tips for keeping your tests clean and maintainable

As your test suite grows, it’s easy for things to become messy. That’s why it’s important to structure your tests in a way that makes sense. This will make your tests more readable, understandable, and maintainable in the long run. Proper test organization helps developers understand the purpose of each test, quickly identify problems, and extend tests as the application evolves.

Here are a few things I’ve found useful to keep your tests clean and maintainable:

1. Name the test files the same as the files being tested

To keep things consistent, your test files should have the same name as the file you want to test. So, you basically take the same file name and add .test to the end. Make sure you don’t forget the file extension at the very end.

For example, if the file is named api.js, the test file should be named api.test.js.

2. Group-related tests using describe blocks

Use describe blocks to group related tests by functionality, feature, or module. This makes it easy to see what is being tested and keeps things organized. For even better clarity, use nested describe blocks to group tests within a broader category. The hierarchical structure in the example below improves readability and makes it easier to locate specific tests.

Example:

By grouping tests logically, you and your teammates can easily navigate the test suite and find relevant tests for specific functionality.

3. Follow the arrange-act-assert (AAA) pattern

Structuring each test case using the AAA pattern improves clarity by separating the setup, execution, and verification phases. It’s a common pattern that helps keep your tests focused and readable.

In the Arrange phase, we set the necessary conditions and inputs for the test. In the Act phase, we execute a function or test a behavior, while in the Assert phase, we verify that the outcome corresponds to expectations.

Example:

This structure helps break the test into digestible chunks and makes it easier to see what’s happening at a glance.

4. Use meaningful test names

The names of your tests should accurately describe what is being tested and help you understand the purpose of each test. This way, when a test fails, it’s immediately clear what went wrong.

TIP: You can use the test or it keyword to write test cases. According to the Jest docs, it is an alias of the test They are exactly the same from a functional point of view. They exist to form a readable sentence from your test.

Good example:

Bad example:

You can see how the first one is much clearer - it is more specific and conveys the purpose of the test better.

5. Make tests independent

Ensure that each test is independent and does not rely on the state or outcome of other tests. This is achieved by clearing mocks using jest.clearAllMocks() in beforeEach and using jest.resetAllMocks() or jest.restoreAllMocks() in afterEach if necessary.

You can also abstract common setup and logic using beforeEach and afterEach hooks to reduce code duplication and improve maintainability.

Example:

Making API calls in JavaScript

In most JavaScript applications, you will use libraries such as axios, fetch, or node-fetch to make HTTP requests. In this blog, we will use axios for our examples.

Example of a basic API call using axios:

Mocking API calls with Jest

Let’s face it: you don’t want your tests to make actual HTTP requests - they could be slow, unreliable, and potentially expensive if you’re dealing with rate limits or expensive APIs. This is where mocking comes into play. With Jest, mocking API calls is super easy.

Here’s a quick example. Let’s say we’re using axios to fetch data from an API. Normally, axios.get would hit the actual API, but in our tests, we want to mock this function and control its behavior.

In order to reset mocks before each test, we are also using beforeEach to ensure there is no interference between tests.

With Jest’s jest.mock(), you can simulate different API responses - whether success or failure - and ensure your code behaves correctly without making an actual network request. This also makes your tests faster.

axios.get has been replaced with a mock function that resolves with the expected data, allowing us to simulate API behavior without making an actual HTTP request. The beforeEach hook resets the mock before each test to ensure that no test depends on the outcome of another. This prevents tests from leaking state and causing breaking failures.

Writing unit tests for GET requests

In the example below, two test cases are written:

  • Test case for a successful GET request.

  • Test case for a failed GET request.

The first test case simulates a successful API request where data is returned as expected. The second test case represents a failed API request. It’s important to test how your code handles errors from the API, such as network failures or non-200 status codes.

Here, the beforeEach hook ensures that mocks are reset before each test to prevent mocked behavior from affecting subsequent tests.

Testing POST requests with Jest

In addition to GET requests, let’s expand our testing to POST requests. POST requests usually involve sending data to an API.

POST request function example

Test case for POST request

In this test, beforeEach ensures that mocks are cleared before each test case. This is crucial when testing multiple functions (e.g., GET and POST requests) to prevent conflicts between mocks.

Testing PUT requests with Jest

PUT requests are typically used to update an existing resource entirely. Let’s create a function to update a user and write corresponding tests.

PUT request function example

Test cases for PUT request

Here, the updateUser function sends a PUT request to update user data. We mock axios.put to simulate successful and unsuccessful update scenarios. Also, beforeEach ensures that each test starts with a fresh mock state.

Testing PATCH requests with Jest

PATCH requests are used to update specific parts of an existing resource. Let’s create a function to partially update a user and write corresponding tests.

PATCH request function example

Test cases for PATCH request:

Here, the partialUpdateUser function sends a PATCH request to partially update user data. We mock axios.patch to simulate both successful and failed partial updates. Additionally, beforeEach ensures that each test starts with a fresh mock state.

Testing DELETE requests with Jest

DELETE requests are used to remove resources from the server. Let’s create a function to delete a user and write corresponding tests.

DELETE request function example

Test cases for DELETE request

Here, the deleteUser function sends a DELETE request to remove the user. We mock axios.delete to simulate successful and unsuccessful deletion scenarios. Additionally, beforeEach ensures that each test starts with a fresh mock state.

How to handle asynchronous code in Jest

One thing we can’t avoid when testing API calls is asynchronous code. Since API calls are asynchronous, it is important to handle asynchronous code properly. Fortunately, Jest makes working with asynchronous code pretty painless, with several ways to handle it. You can use beforeEach for setup, while other ways include using async/await, then/catch for promises, or the done callback for complex asynchronous code.

Using beforeEach with asynchronous code

If any setup is needed before each test, such as preparing mock data or resetting async functions, you can use async in your beforeEach.

In this example, beforeEach can handle an asynchronous setup. A mock response is prepared asynchronously before each test run.

Other methods to handle asynchronous code

Using async/await:

Using .then() and .catch() for promises:

Using done callback for complex async code:

Personally, I prefer to use the async/await method to handle asynchronous code when writing the Jest test because it's the simplest and makes the code more readable compared to other methods.

Error handling in API tests

We all know API calls don’t always go smoothly - whether it’s a network error or a 500 internal server error, you need to ensure your code handles failures properly. You can simulate different error conditions in your tests, such as network failures or unexpected HTTP status codes, to ensure that your code handles these scenarios gracefully.

Using afterEachfor cleanup

When mocking modules or global variables, use afterEach to clean up and restore the environment.

We mocked error responses such as network errors or HTTP status codes (500 in this case) to see how our API function handles errors. afterEachis used to restore the original implementation of all mocked functions after each test, ensuring that tests do not interfere with each other. If the mock changes in one test, afterEach will reset it for the next one.

By mocking errors, you can test how your code reacts to all kinds of failure scenarios - things like network timeouts, 404s, or even unexpected forms of data. Trust me, your future self will thank you when a real error occurs and your code handles it smoothly.

Advanced Jest features for API testing

Using jest.spyOn() for partial mocks

Sometimes, you may want to mock or spy only certain functions within a module and leave other parts of the module intact. jest.spyOn() is useful for this.

Specifically, in this example, jest.spyOn() allows calls to specific functions to be monitored without overriding the entire module. This is particularly useful when you want to test the behavior of a module while still observing certain internal function calls. mockRestore() in afterEach ensures that any spying or mocking done within the test is restored after the test ends.

Using jest.fn() for custom mocking

You can use jest.fn() to create a mock function with custom behavior. This can be useful when testing different return values or side effects.

Generally speaking, jest.fn() allows for more granular control of mock functions. In this example, mockAxiosGet replaces axios.get, and we can customize its behavior for different tests. In terms of custom behavior, you can define specific return values, such as using .mockResolvedValueOnce() to return different results for different calls within the same test suite.

JEST - popular javascript testing framework

Final words on JEST API testing

Testing API calls with Jest is a crucial step in ensuring the stability and reliability of your applications. By leveraging Jest’s powerful features—such as mocking, hooks like beforeEach and afterEach, and simulating success and failure scenarios—you can create a robust test suite for your API interactions.

In this blog, we explored how to mock API calls with Jest, allowing you to test your API logic without relying on actual network requests. We also covered best practices for handling asynchronous code, using async/await to make tests readable and reliable. Structuring tests for clarity and maintainability is essential for keeping your code organized, whether by applying the Arrange-Act-Assert pattern or logically grouping related tests.

The importance of beforeEach and afterEach hooks was emphasized, as they ensure each test starts with a clean state, avoiding cross-test interference. On top of that, we’ve delved into advanced techniques like jest.spyOn() and jest.fn() for fine-tuned control over mocks, helping you customize mock behavior or precisely monitor method calls.

Lastly, we demonstrated how to extend your tests to cover all HTTP methods - GET, POST, PUT, PATCH, and DELETE - so your entire API is thoroughly tested, giving you confidence that each type of request is working as expected.

In summary, Jest simplifies API testing by providing a comprehensive set of tools for mocking, spying, and handling synchronous and asynchronous code. By following the best practices outlined here, you can build a reliable test suite that ensures your APIs behave as expected, even in the most challenging scenarios.

I hope these tips help you write more maintainable and effective API tests with Jest! Trust me, investing in good test practices now saves you a ton of headaches down the line.

Spread the word:
Keep readingSimilar blogs for further insights
Java Design Patterns: Tips, Challenges, and Best Practices
Technology
Vladimir Š.9 min readDec 20, 2024
Java Design Patterns: Tips, Challenges, and Best PracticesJava Design Patterns may seem complex at first, but once you grasp their underlying principles, they can help you organize your code for better scalability, make it easier to maintain, improve efficiency, and more.
How JavaScript Signals Are Changing Everyday Development
Technology
Hrvoje D.5 min readNov 7, 2024
How JavaScript Signals Are Changing Everyday DevelopmentSignals are getting popular lately, but why is that? Read the blog to discover how signals in JavaScript are transforming code to be more concise, readable, and understandable.
Building Our New Website with Next.js: The Benefits and Challenges
Technology
Mario F.Luka C.5 min readOct 18, 2024
Building Our New Website with Next.js: The Benefits and ChallengesWe decided to rewrite our website, focusing on design and maintainability. Read why we chose Next.js.