Mock Dynamic Axios Responses with Query Params

Mocking dynamic API responses based on query parameters in Axios allows developers to test various application states, edge cases, and pagination flows locally without relying on an active backend. This guide demonstrates how to inspect incoming request parameters dynamically and return customized responses using axios-mock-adapter as well as native Axios custom adapters.

Using axios-mock-adapter for Dynamic Responses

The standard and most flexible tool for mocking Axios requests is the axios-mock-adapter library. It allows you to pass a callback function to the .reply() method, exposing the request configuration object (config), which contains the query parameters.

1. Installation

Install the library in your project:

npm install axios-mock-adapter --save-dev

2. Implementation

Attach the mock adapter to your Axios instance and inspect config.params within the .reply() callback:

import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';

// Create an Axios instance
const apiClient = axios.create({
  baseURL: 'https://api.example.com',
});

// Initialize the mock adapter
const mock = new MockAdapter(apiClient);

// Mock a dynamic GET request based on query parameters
mock.onGet('/users').reply((config) => {
  const { role, page = 1, limit = 10 } = config.params || {};

  // Mock dataset
  const users = [
    { id: 1, name: 'Alice', role: 'admin' },
    { id: 2, name: 'Bob', role: 'editor' },
    { id: 3, name: 'Charlie', role: 'admin' },
    { id: 4, name: 'David', role: 'viewer' },
  ];

  // Filter based on query params
  let filteredUsers = users;
  if (role) {
    filteredUsers = users.filter((user) => user.role === role);
  }

  // Handle pagination
  const startIndex = (page - 1) * limit;
  const paginatedUsers = filteredUsers.slice(startIndex, startIndex + Number(limit));

  // Return status code and response payload
  return [
    200,
    {
      data: paginatedUsers,
      total: filteredUsers.length,
      page: Number(page),
    },
  ];
});

3. Making Requests

When making requests through the Axios instance, the adapter intercepts the call and serves data matching the passed query parameters:

async function fetchUsers() {
  // Returns only admin users
  const adminResponse = await apiClient.get('/users', {
    params: { role: 'admin' },
  });
  console.log(adminResponse.data);

  // Returns paginated results
  const pagedResponse = await apiClient.get('/users', {
    params: { page: 2, limit: 2 },
  });
  console.log(pagedResponse.data);
}

fetchUsers();

Dynamic Matching with params Matching Rules

axios-mock-adapter also supports defining specific routes for exact parameter matches before falling back to a general handler:

// Exact match for ?role=guest
mock.onGet('/users', { params: { role: 'guest' } }).reply(200, {
  data: [],
  message: 'No guests found',
});

// Fallback for any other /users query
mock.onGet('/users').reply(200, {
  data: [{ id: 99, name: 'Default User' }],
});

Alternative: Mocking via Custom Axios Adapter

If you prefer zero external dependencies, you can define a custom adapter directly on your Axios configuration:

import axios from 'axios';

const mockAdapter = async (config) => {
  const { url, params } = config;

  if (url === '/products') {
    const category = params?.category;
    
    if (category === 'electronics') {
      return {
        data: [{ id: 101, name: 'Laptop' }],
        status: 200,
        statusText: 'OK',
        headers: {},
        config,
      };
    }

    return {
      data: [{ id: 201, name: 'Generic Item' }],
      status: 200,
      statusText: 'OK',
      headers: {},
      config,
    };
  }

  return Promise.reject(new Error(`Unhandled route: ${url}`));
};

const api = axios.create({ adapter: mockAdapter });

Simulating Errors and Latency

To build realistic tests, you can conditionally simulate HTTP errors or network delays based on query flags:

mock.onGet('/status').reply(async (config) => {
  const { shouldFail, delay } = config.params || {};

  if (delay) {
    await new Promise((resolve) => setTimeout(resolve, Number(delay)));
  }

  if (shouldFail === 'true') {
    return [500, { error: 'Internal Server Error' }];
  }

  return [200, { status: 'Operational' }];
});