When testing code that uses axios, you need to master unit testing, integration testing, and mocking techniques.
1. Using Jest and axios-mock-adapter
Install Dependencies
bashnpm install --save-dev jest axios-mock-adapter @testing-library/react
Basic Mock Testing
javascript// api/user.js import axios from 'axios'; export const fetchUser = async (userId) => { const response = await axios.get(`/api/users/${userId}`); return response.data; }; export const createUser = async (userData) => { const response = await axios.post('/api/users', userData); return response.data; }; // __tests__/user.test.js import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; import { fetchUser, createUser } from '../api/user'; describe('User API', () => { let mock; beforeEach(() => { mock = new MockAdapter(axios); }); afterEach(() => { mock.restore(); }); test('fetchUser should return user data', async () => { const userData = { id: 1, name: 'John', email: 'john@example.com' }; mock.onGet('/api/users/1').reply(200, userData); const result = await fetchUser(1); expect(result).toEqual(userData); }); test('fetchUser should handle error', async () => { mock.onGet('/api/users/999').reply(404, { message: 'User not found' }); await expect(fetchUser(999)).rejects.toThrow(); }); test('createUser should create new user', async () => { const newUser = { name: 'Jane', email: 'jane@example.com' }; const createdUser = { id: 2, ...newUser }; mock.onPost('/api/users').reply(201, createdUser); const result = await createUser(newUser); expect(result).toEqual(createdUser); }); });
Advanced Mock Configuration
javascript// __tests__/api.test.js import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; describe('API Testing', () => { let mock; beforeEach(() => { mock = new MockAdapter(axios); }); afterEach(() => { mock.restore(); }); test('mock network error', async () => { mock.onGet('/api/data').networkError(); await expect(axios.get('/api/data')).rejects.toThrow('Network Error'); }); test('mock timeout', async () => { mock.onGet('/api/data').timeout(); await expect(axios.get('/api/data')).rejects.toThrow('timeout'); }); test('mock with function', async () => { mock.onPost('/api/users').reply((config) => { const data = JSON.parse(config.data); if (!data.email) { return [400, { error: 'Email is required' }]; } return [201, { id: 1, ...data }]; }); // Test success const response = await axios.post('/api/users', { name: 'John', email: 'john@example.com' }); expect(response.status).toBe(201); // Test failure await expect( axios.post('/api/users', { name: 'John' }) ).rejects.toThrow(); }); test('mock with headers', async () => { mock.onGet('/api/protected', { headers: { Authorization: 'Bearer token123' } }).reply(200, { data: 'protected' }); const response = await axios.get('/api/protected', { headers: { Authorization: 'Bearer token123' } }); expect(response.data).toEqual({ data: 'protected' }); }); test('mock with query params', async () => { mock.onGet('/api/search', { params: { q: 'test' } }) .reply(200, { results: [] }); const response = await axios.get('/api/search', { params: { q: 'test' } }); expect(response.data).toEqual({ results: [] }); }); });
2. Using MSW (Mock Service Worker)
Installation and Configuration
bashnpm install --save-dev msw
javascript// mocks/handlers.js import { rest } from 'msw'; export const handlers = [ // GET request rest.get('/api/users', (req, res, ctx) => { return res( ctx.status(200), ctx.json([ { id: 1, name: 'John' }, { id: 2, name: 'Jane' } ]) ); }), // GET single user rest.get('/api/users/:id', (req, res, ctx) => { const { id } = req.params; if (id === '999') { return res( ctx.status(404), ctx.json({ message: 'User not found' }) ); } return res( ctx.status(200), ctx.json({ id: Number(id), name: 'John' }) ); }), // POST request rest.post('/api/users', async (req, res, ctx) => { const body = await req.json(); return res( ctx.status(201), ctx.json({ id: 3, ...body }) ); }), // PUT request rest.put('/api/users/:id', async (req, res, ctx) => { const { id } = req.params; const body = await req.json(); return res( ctx.status(200), ctx.json({ id: Number(id), ...body }) ); }), // DELETE request rest.delete('/api/users/:id', (req, res, ctx) => { return res(ctx.status(204)); }) ]; // mocks/server.js import { setupServer } from 'msw/node'; import { handlers } from './handlers'; export const server = setupServer(...handlers); // jest.setup.js import { server } from './mocks/server'; beforeAll(() => server.listen()); afterEach(() => server.resetHandlers()); afterAll(() => server.close());
Testing with MSW
javascript// __tests__/user.integration.test.js import { fetchUser, createUser, updateUser, deleteUser } from '../api/user'; describe('User API Integration Tests', () => { test('should fetch all users', async () => { const users = await fetchUsers(); expect(users).toHaveLength(2); expect(users[0]).toHaveProperty('id'); expect(users[0]).toHaveProperty('name'); }); test('should fetch single user', async () => { const user = await fetchUser(1); expect(user).toEqual({ id: 1, name: 'John' }); }); test('should handle 404 error', async () => { await expect(fetchUser(999)).rejects.toThrow(); }); test('should create user', async () => { const newUser = { name: 'Bob', email: 'bob@example.com' }; const created = await createUser(newUser); expect(created).toMatchObject(newUser); expect(created).toHaveProperty('id'); }); test('should update user', async () => { const updates = { name: 'John Updated' }; const updated = await updateUser(1, updates); expect(updated.name).toBe('John Updated'); }); test('should delete user', async () => { await expect(deleteUser(1)).resolves.not.toThrow(); }); });
Dynamic Handler Override
javascript// __tests__/dynamic-mock.test.js import { rest } from 'msw'; import { server } from '../mocks/server'; test('should handle server error', async () => { // Temporarily override handler server.use( rest.get('/api/users', (req, res, ctx) => { return res( ctx.status(500), ctx.json({ error: 'Internal Server Error' }) ); }) ); await expect(fetchUsers()).rejects.toThrow(); }); test('should handle network error', async () => { server.use( rest.get('/api/users', (req, res) => { return res.networkError('Failed to connect'); }) ); await expect(fetchUsers()).rejects.toThrow('Failed to connect'); });
3. React Component Testing
Using React Testing Library
javascript// components/UserProfile.jsx import React, { useEffect, useState } from 'react'; import axios from 'axios'; export const UserProfile = ({ userId }) => { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { const fetchUser = async () => { try { setLoading(true); const response = await axios.get(`/api/users/${userId}`); setUser(response.data); } catch (err) { setError(err.message); } finally { setLoading(false); } }; fetchUser(); }, [userId]); if (loading) return <div>Loading...</div>; if (error) return <div>Error: {error}</div>; return ( <div> <h1>{user.name}</h1> <p>{user.email}</p> </div> ); }; // __tests__/UserProfile.test.jsx import React from 'react'; import { render, screen, waitFor } from '@testing-library/react'; import { UserProfile } from '../components/UserProfile'; import { server } from '../mocks/server'; import { rest } from 'msw'; describe('UserProfile Component', () => { test('should display loading state initially', () => { render(<UserProfile userId={1} />); expect(screen.getByText('Loading...')).toBeInTheDocument(); }); test('should display user data after loading', async () => { render(<UserProfile userId={1} />); await waitFor(() => { expect(screen.getByText('John')).toBeInTheDocument(); }); expect(screen.getByText('john@example.com')).toBeInTheDocument(); }); test('should display error message on failure', async () => { server.use( rest.get('/api/users/999', (req, res, ctx) => { return res(ctx.status(404)); }) ); render(<UserProfile userId={999} />); await waitFor(() => { expect(screen.getByText(/Error:/)).toBeInTheDocument(); }); }); });
4. Vue Component Testing
Using Vue Test Utils
javascript// components/UserProfile.vue <template> <div> <div v-if="loading">Loading...</div> <div v-else-if="error">Error: {{ error }}</div> <div v-else> <h1>{{ user.name }}</h1> <p>{{ user.email }}</p> </div> </div> </template> <script> import { ref, onMounted } from 'vue'; import axios from 'axios'; export default { props: ['userId'], setup(props) { const user = ref(null); const loading = ref(true); const error = ref(null); onMounted(async () => { try { const response = await axios.get(`/api/users/${props.userId}`); user.value = response.data; } catch (err) { error.value = err.message; } finally { loading.value = false; } }); return { user, loading, error }; } }; </script> // __tests__/UserProfile.spec.js import { mount } from '@vue/test-utils'; import { describe, it, expect } from 'vitest'; import UserProfile from '../components/UserProfile.vue'; import { server } from '../mocks/server'; describe('UserProfile', () => { it('should display loading state initially', () => { const wrapper = mount(UserProfile, { props: { userId: 1 } }); expect(wrapper.text()).toContain('Loading...'); }); it('should display user data after loading', async () => { const wrapper = mount(UserProfile, { props: { userId: 1 } }); await wrapper.vm.$nextTick(); await new Promise(resolve => setTimeout(resolve, 0)); expect(wrapper.text()).toContain('John'); }); });
5. Custom Hook/Composable Testing
javascript// hooks/useApi.js import { useState, useEffect } from 'react'; import axios from 'axios'; export const useApi = (url) => { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { const fetchData = async () => { try { setLoading(true); const response = await axios.get(url); setData(response.data); } catch (err) { setError(err); } finally { setLoading(false); } }; fetchData(); }, [url]); return { data, loading, error }; }; // __tests__/useApi.test.js import { renderHook, waitFor } from '@testing-library/react'; import { useApi } from '../hooks/useApi'; describe('useApi Hook', () => { test('should return loading state initially', () => { const { result } = renderHook(() => useApi('/api/data')); expect(result.current.loading).toBe(true); expect(result.current.data).toBeNull(); expect(result.current.error).toBeNull(); }); test('should return data after successful request', async () => { const { result } = renderHook(() => useApi('/api/users')); await waitFor(() => { expect(result.current.loading).toBe(false); }); expect(result.current.data).toHaveLength(2); expect(result.current.error).toBeNull(); }); test('should return error on failed request', async () => { const { result } = renderHook(() => useApi('/api/error')); await waitFor(() => { expect(result.current.loading).toBe(false); }); expect(result.current.error).not.toBeNull(); expect(result.current.data).toBeNull(); }); });
6. E2E Testing
Using Cypress
javascript// cypress/integration/api.spec.js describe('API Tests', () => { beforeEach(() => { // Intercept API requests cy.intercept('GET', '/api/users', { statusCode: 200, body: [ { id: 1, name: 'John' }, { id: 2, name: 'Jane' } ] }).as('getUsers'); }); it('should display users list', () => { cy.visit('/users'); cy.wait('@getUsers'); cy.get('[data-testid="user-list"]').should('have.length', 2); cy.contains('John').should('be.visible'); }); it('should handle API error', () => { cy.intercept('GET', '/api/users', { statusCode: 500, body: { error: 'Server Error' } }).as('getUsersError'); cy.visit('/users'); cy.wait('@getUsersError'); cy.contains('Error loading users').should('be.visible'); }); it('should create new user', () => { cy.intercept('POST', '/api/users', { statusCode: 201, body: { id: 3, name: 'New User' } }).as('createUser'); cy.visit('/users/new'); cy.get('input[name="name"]').type('New User'); cy.get('button[type="submit"]').click(); cy.wait('@createUser').its('request.body').should('deep.equal', { name: 'New User' }); cy.url().should('include', '/users'); }); });
7. Testing Best Practices
Test File Organization
shellsrc/ ├── api/ │ ├── user.js │ └── __tests__/ │ └── user.test.js ├── components/ │ ├── UserProfile.jsx │ └── __tests__/ │ └── UserProfile.test.jsx ├── hooks/ │ ├── useApi.js │ └── __tests__/ │ └── useApi.test.js └── mocks/ ├── handlers.js └── server.js
Test Utility Functions
javascript// test-utils.js import { render } from '@testing-library/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; export const createTestQueryClient = () => new QueryClient({ defaultOptions: { queries: { retry: false, }, }, }); export function renderWithClient(ui) { const testQueryClient = createTestQueryClient(); const { rerender, ...result } = render( <QueryClientProvider client={testQueryClient}>{ui}</QueryClientProvider> ); return { ...result, rerender: (rerenderUi) => rerender( <QueryClientProvider client={testQueryClient}>{rerenderUi}</QueryClientProvider> ), }; }
Testing Strategy Summary
| Test Type | Tools | Applicable Scenarios |
|---|---|---|
| Unit Testing | Jest + axios-mock-adapter | Testing API functions |
| Integration Testing | MSW | Testing component and API interaction |
| Component Testing | React Testing Library / Vue Test Utils | Testing UI components |
| Hook Testing | React Testing Library | Testing custom Hooks |
| E2E Testing | Cypress / Playwright | End-to-end testing |
Best Practices
- Use MSW: Recommended for integration testing, closer to real environment
- Separate Concerns: Separate testing logic from UI
- Clean Up Side Effects: Clean up mocks after each test
- Test Error Scenarios: Test failures as well as successes
- Avoid Real Requests: Should not send real HTTP requests during testing
- Use Data Attributes: Use data-testid to select elements
- Keep Tests Independent: Each test should run independently