In modern web development, testing API integrations is crucial for ensuring application reliability. Combining TypeScript with Testing Library and Axios provides a robust environment for writing comprehensive tests that are both type-safe and easy to maintain.

Setting Up the Environment

Before diving into testing, ensure your project has the necessary dependencies installed. You will need TypeScript, Testing Library (specifically @testing-library/react), Axios, and related type definitions.

  • typescript
  • @testing-library/react
  • @testing-library/jest-dom
  • axios
  • @types/jest
  • @types/node

Install these via npm or yarn:

npm install --save-dev typescript @testing-library/react @testing-library/jest-dom axios @types/jest @types/node

Creating a TypeScript API Service

Start by creating a service to handle API calls with Axios. Using TypeScript, define interfaces for data structures to ensure type safety.

Example:

// apiService.ts
import axios from 'axios';

export interface User {
  id: number;
  name: string;
  email: string;
}

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

export const fetchUser = async (userId: number): Promise => {
  const response = await apiClient.get(`/users/${userId}`);
  return response.data;
};

Writing Tests with Testing Library and Jest

To test API interactions, mock Axios responses to simulate server data. Use jest.mock to replace Axios with a mock implementation.

Example test:

// apiService.test.ts
import { fetchUser, User } from './apiService';
import axios from 'axios';

jest.mock('axios');
const mockedAxios = axios as jest.Mocked;

describe('fetchUser', () => {
  it('fetches user data successfully', async () => {
    const mockUser: User = { id: 1, name: 'John Doe', email: '[email protected]' };
    mockedAxios.get.mockResolvedValue({ data: mockUser });

    const user = await fetchUser(1);

    expect(mockedAxios.get).toHaveBeenCalledWith('/users/1');
    expect(user).toEqual(mockUser);
  });

  it('handles errors gracefully', async () => {
    mockedAxios.get.mockRejectedValue(new Error('Network Error'));

    await expect(fetchUser(1)).rejects.toThrow('Network Error');
  });
});

Integrating with React Components

Use Testing Library to render components that fetch data on mount. Mock API calls to test different scenarios.

Example:

// UserComponent.tsx
import React, { useEffect, useState } from 'react';
import { fetchUser, User } from './apiService';

const UserComponent: React.FC<{ userId: number }> = ({ userId }) => {
  const [user, setUser] = useState(null);
  const [error, setError] = useState(null);

  useEffect(() => {
    fetchUser(userId)
      .then(data => setUser(data))
      .catch(() => setError('Failed to load user'));
  }, [userId]);

  if (error) {
    return 
{error}
; } if (!user) { return
Loading...
; } return (

{user.name}

Email: {user.email}

); }; export default UserComponent;

Testing the React Component

Mock the fetchUser API call to test the component's behavior under different conditions.

// UserComponent.test.tsx
import React from 'react';
import { render, screen, waitFor } from '@testing-library/react';
import UserComponent from './UserComponent';
import * as apiService from './apiService';

jest.mock('./apiService');

describe('UserComponent', () => {
  it('renders user data correctly', async () => {
    const mockUser = { id: 2, name: 'Jane Smith', email: '[email protected]' };
    jest.spyOn(apiService, 'fetchUser').mockResolvedValueOnce(mockUser);

    render();

    expect(screen.getByTestId('loading')).toBeInTheDocument();

    await waitFor(() => {
      expect(screen.getByTestId('user')).toBeInTheDocument();
      expect(screen.getByText('Jane Smith')).toBeInTheDocument();
    });
  });

  it('displays error message on failure', async () => {
    jest.spyOn(apiService, 'fetchUser').mockRejectedValueOnce(new Error('Error fetching'));

    render();

    await waitFor(() => {
      expect(screen.getByTestId('error')).toHaveTextContent('Failed to load user');
    });
  });
});

Best Practices and Tips

When working with TypeScript, always define interfaces for API data to catch errors early. Mock API responses accurately to simulate real-world scenarios. Use Testing Library's querying functions to interact with DOM elements as users would.

Maintain clear separation between API logic and UI components. Write tests that cover both success and failure cases to ensure robustness.

Conclusion

Integrating TypeScript with Testing Library and Axios enhances your testing strategy by providing type safety, realistic API simulations, and user-centric testing approaches. This combination leads to more reliable, maintainable, and scalable codebases.