TL;DR: API mocking simplifies testing by simulating API responses, improving performance, reliability, and control. This blog contains strategies and best practices for unit testing.
Software testing and verification are essential for modern development, but the complexity of customer requirements and third-party integrations can make them daunting. Although these third-party integrations improve app functionality, testing them can be a nightmare for developers. There can be issues with security, performance, data incompatibilities, error handling, dependencies, and more.
As a result, developers are now using various mocking techniques to address these issues. This article will explain why mocking is so popular, and how API requests can be mocked to improve unit test coverage.
Mocking API requests becomes an invaluable strategy for overcoming these challenges. It allows you to simulate API responses and test features without sending network requests to external APIs. By eliminating actual request processing, mocking boosts performance, reliability, security, and control over data during testing.
As part of their jobs, it falls on the shoulders of developers to perform unit tests while developing software. These tests must be executed to give all results in the shortest time possible. API mocking ensures the testing cycle is free of excess delays related to external servers and latency due to networks. Tests have quick and consistent run times.
When testing using real API calls, exchanging large amounts of data can make the tests unpredictable. It is advisable to use mocking because it permits developers to state what they want the responses of tests to be, thus avoiding surprises in responses that may not be consistent with normal tests.
Unit tests focus on verifying the behaviors of small parts of your app. Mocking can isolate these components and test them separately without depending on any other components, making your tests more focused and meaningful.
There are several strategies for creating a Mock API. There is no best strategy, so you can choose the strategy most suitable to your needs. The complexity you want to extend with your API will be a factor in choosing the right strategy. You can look at the following strategies and identify their apps.
Manual mocks involve creating mock functions or modules that replace the real HTTP request mechanisms. This can be as simple as overriding a function that makes an HTTP request with one that returns a predefined (hard-coded) response.
// Example of a manual mock. const fetchUserFromMockApi = (userId) => { return Promise.resolve({ id: userId, name: "Mock User" }); }; // Inject the mock function. const fetchUser = createFetchUser(fetchUserFromMockApi); // Test function. const runTests = () => { fetchUser(1) .then((user) => { if (user.id === 1 && user.name === "Mock User") { console.log("Test passed"); } else { console.log("Test failed"); } }) .catch((error) => { console.error("Test failed with error:", error); }); };
The results of the previous implementation are shown in the following screenshot.
You can find the full implementation with this link.
While manual mocks offer great flexibility and control, they can become hard to manage with complex APIs or when used extensively across test suites. Also, they are time-consuming as the complexity increases.
Several libraries and tools have been developed to streamline the process of mocking API requests. Libraries such as Nock for Node.js, Mockito for Java, and Responses for Python have rich APIs for mocking.
Nock example
const nock = require('nock'); const chai = require('chai'); const fetchUser = require('../src/userService'); const { expect } = chai; describe('fetchUser', () => { before(() => { // Mocking an HTTP GET request to a user service. nock('https://api.example.com') .get('/users/1') .reply(200, { id: 1, name: 'Mock User' }); }); it('should return a mocked user', async () => { const user = await fetchUser(1); expect(user).to.deep.equal({ id: 1, name: 'Mock User' }); }); after(() => { nock.cleanAll(); }); });
The code in the previous implementation produces the results shown in the following screenshot.
The complete code example is available here.
Regardless of the method you use to mock API requests, there must be a process to follow. The reason is that the validity of unit tests depends on what we build here. So, we decided to include a checklist to help you create a Mock API with minimal deviation from the real API:
Start by identifying the external services and endpoints with which your app interacts. Think about which interactions affect the outcomes of your unit tests and should be mocked.
Based on your API interaction’s complexity and project needs, choose a mocking strategy. Manual mocks may be sufficient for simple scenarios, while more complex integrations could benefit from a mocking library or service virtualization.
The way you execute your plan depends on the strategy choice. Ensure that your trial runs accurately and fits the responses given to your app with the expected response from the call to the live API. This includes both successful responses and error situations.
After preparing your mock objects, you can include them in your test suite with your mock functions or configure a mocking library within your test setup. If you are using service virtualization, start the virtual service or tool before running your test suite.
After running your tests with the mocked APIs, carefully observe any failures or unexpected behavior, as these can expose differences between your mock implementations and the real API behavior. You can adjust your mocks to ensure they cover the testing scenarios accurately.
When performing unit testing using the mock API, follow some good practices. These practices will help developers minimize API overfitting and nonfunctional issues.
Your mocks are only as good as their fidelity to the behavior of the real API. A mock must support the same transport protocols, schemes, etc., as the real API. The mock API should be accessible exactly like the original service and require no special tools or libraries.
While mocking allows you to use static responses, using data that’s too simplistic can lead to tests that pass despite real-world scenarios that would cause failures. Ensure your mocked responses include realistic data.
Simulate unexpected errors, long response times, or even invalid messages, and make sure your API client handles them gracefully. This is a way of testing the potential non-functionalities of the client or your software.
Mocking API calls is an effective way to achieve comprehensive unit test coverage for apps that depend on external services. It isolates your unit tests from external interactions, making them quick, consistent, and clean.
Whether you opt for manual mocks, mock libraries, or service virtualization, the key to successful mocking is accurately emulating the real-world APIs your app relies on. However, it’s good to stay informed and open to changes to refine your mocking strategies and get the best result for your project.