Testing Axios Interceptors in Isolation
Testing Axios HTTP client interceptors in isolation ensures that request headers, authentication tokens, global response transformations, and error-handling routines function correctly without relying on real network calls. The industry-standard approach involves either extracting the interceptor callback functions as pure units or using a mock adapter library to simulate HTTP traffic against a configured Axios instance. This guide details both methodologies to help you effectively test request and response interceptors.
Approach 1: Extracting Interceptor Handlers (Unit Testing)
The most direct and isolated way to test interceptors is to define the handler functions independently from the Axios instance registration. Because interceptors are simply functions that take a configuration or response object and return modified data or rejected promises, they can be tested as standard unit functions.
1. Structure the Interceptors
Define the fulfilled and rejected handlers in a dedicated file:
// authInterceptor.js
export const onRequestFulfilled = (config) => {
const token = localStorage.getItem('authToken');
if (token) {
config.headers = config.headers || {};
config.headers.Authorization = `Bearer ${token}`;
}
return config;
};
export const onRequestRejected = (error) => {
return Promise.reject(error);
};
export const onResponseRejected = (error) => {
if (error.response && error.response.status === 401) {
// Custom redirect or logout logic
window.location.href = '/login';
}
return Promise.reject(error);
};2. Test the Handlers
Test these functions directly using test runners like Jest or Vitest by asserting input transformations and promise rejections:
// authInterceptor.test.js
import { onRequestFulfilled, onResponseRejected } from './authInterceptor';
describe('Axios Interceptors - Unit Tests', () => {
beforeEach(() => {
localStorage.clear();
});
test('adds Authorization header when token is present', () => {
localStorage.setItem('authToken', 'test-token-123');
const initialConfig = { headers: {} };
const result = onRequestFulfilled(initialConfig);
expect(result.headers.Authorization).toBe('Bearer test-token-123');
});
test('returns original config when token is absent', () => {
const initialConfig = { headers: {} };
const result = onRequestFulfilled(initialConfig);
expect(result.headers.Authorization).toBeUndefined();
});
test('redirects to login on 401 response status', async () => {
delete window.location;
window.location = { href: '' };
const mockError = {
response: { status: 401 }
};
await expect(onResponseRejected(mockError)).rejects.toEqual(mockError);
expect(window.location.href).toBe('/login');
});
});Approach
2: Using axios-mock-adapter (Integration-style
Isolation)
When you need to verify that interceptors are properly attached to an
Axios instance and execute in the correct order, use
axios-mock-adapter. This library intercepts requests at the
adapter level, preventing actual network requests while keeping the
Axios pipeline intact.
1. Configure the Axios Client
// apiClient.js
import axios from 'axios';
import { onRequestFulfilled, onResponseRejected } from './authInterceptor';
const apiClient = axios.create({
baseURL: 'https://api.example.com',
});
apiClient.interceptors.request.use(onRequestFulfilled);
apiClient.interceptors.response.use((res) => res, onResponseRejected);
export default apiClient;2. Test with the Mock Adapter
// apiClient.test.js
import MockAdapter from 'axios-mock-adapter';
import apiClient from './apiClient';
describe('Axios Instance Interceptors', () => {
let mock;
beforeEach(() => {
mock = new MockAdapter(apiClient);
localStorage.clear();
});
afterEach(() => {
mock.restore();
});
test('attaches token to outgoing requests', async () => {
localStorage.setItem('authToken', 'secure-jwt');
mock.onGet('/users').reply((config) => {
expect(config.headers.Authorization).toBe('Bearer secure-jwt');
return [200, { id: 1, name: 'Alice' }];
});
const response = await apiClient.get('/users');
expect(response.data.name).toBe('Alice');
});
test('handles 401 errors through the response interceptor', async () => {
delete window.location;
window.location = { href: '' };
mock.onGet('/protected').reply(401);
await expect(apiClient.get('/protected')).rejects.toThrow();
expect(window.location.href).toBe('/login');
});
});Summary of Best Practices
- Separate Handler Logic: Keep the transformation and error-handling functions decoupled from instance creation to maximize unit test coverage.
- Test Both Fulfillment and Rejection: Ensure every
interceptor has tests for normal execution paths as well as error
propagation via
Promise.reject. - Avoid Real HTTP Calls: Use
axios-mock-adapteror mock the adapter layer directly to test instance bindings deterministically and fast.