Your Image Alt Text
Jazzed Technology Blog

How to Unit Test React Context API Authentication with Vitest | A Comprehensive Tutorial

Introduction

When working with React applications, the Context API is a powerful tool for managing global state. One common use case is authentication, where an AuthProvider component manages user login, logout, and user details. However, testing components that depend on useContext can be tricky.

In this article, we’ll explore unit testing the Context API in React with TypeScript using Vitest. We’ll focus on testing a profile component that fetches user details from an AuthProvider and conditionally renders content based on the authentication state.

By the end of this tutorial, you’ll learn how to:

✅ Mock context values in Vitest ✅ Test components that rely on the Context API ✅ Simulate different authentication states

Understanding AuthProvider

Before diving into tests, let’s understand the role of AuthProvider. It typically handles user authentication by:

  • Storing user details (name, email, ID, etc.)
  • Providing login and logout functions
  • Returning the user object to child components
  • Checking if a user is authenticated

Here’s a basic example of an AuthProvider:

import React, { createContext, useContext, useState, ReactNode } from 'react';
interface User {
  firstName: string;
  lastName: string;
  userName: string;
  email: string;
}
interface AuthContextType {
  user: User | null;
  login: (userData: User) => void;
  logout: () => void;
}
const AuthContext = createContext<AuthContextType | undefined>(undefined);
export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
  const [user, setUser] = useState<User | null>(null);
  const login = (userData: User) => setUser(userData);
  const logout = () => setUser(null);
  return (
    <AuthContext.Provider value={{ user, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
};
export const useAuth = () => {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error('useAuth must be used within an AuthProvider');
  }
  return context;
};

The MyProfile Component

Now, let’s look at the MyProfile component. It fetches user details from useAuth and conditionally renders either a loading state or user details.

import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { SideNavLink, Loading } from '@carbon/react';
import * as Icons from '@carbon/icons-react';
import AvatarImage from '../AvatarImage';
import { useThemePreference } from '../../utils/ThemePreference';
import { useAuth } from '../../contexts/AuthProvider';
const MyProfile = () => {
  const { user, logout } = useAuth();
  const { theme, setTheme } = useThemePreference();
  const navigate = useNavigate();
  const [goTo, setGoTo] = useState(false);
  const [goToURL, setGoToURL] = useState('');
  useEffect(() => {
    if (goTo) {
      setGoTo(false);
      navigate(goToURL);
    }
  }, [goTo]);
  const changeTheme = () => {
    const newTheme = theme === 'g10' ? 'g100' : 'g10';
    setTheme(newTheme);
    localStorage.setItem('mode', newTheme === 'g10' ? 'light' : 'dark');
  };
  return user ? (
    <>
      <div className="user-info-section">
        <AvatarImage userName={`${user.firstName} ${user.lastName}`} size="large" />
        <div className="user-data">
          <p className="user-name">{`${user.firstName} ${user.lastName}`}</p>
          <p>{`Username: ${user.userName}`}</p>
          <p>{`Email: ${user.email}`}</p>
        </div>
      </div>
      <hr className="divisory" />
      <nav className="account-nav">
        <SideNavLink renderIcon={theme === 'g10' ? Icons.Asleep : Icons.Light} onClick={changeTheme}>
          Change theme
        </SideNavLink>
        <SideNavLink renderIcon={Icons.UserFollow} onClick={logout}>
          Log out
        </SideNavLink>
      </nav>
    </>
  ) : (
    <Loading description="Loading user details" withOverlay />
  );
};
export default MyProfile;

Unit Testing MyProfile with Vitest

To test this component, we need to mock useAuth so we can control the returned user data.

import React from 'react';
import MyProfile from '../../components/MyProfile';
import { render, screen } from '@testing-library/react';
import { describe, expect, it, vi } from 'vitest';
import { AuthProvider, useAuth } from '../../contexts/AuthProvider';
import { ThemePreference } from '../../utils/ThemePreference';
import { BrowserRouter } from 'react-router-dom';
const renderComponent = () => {
  render(
    <ThemePreference>
      <AuthProvider>
        <BrowserRouter>
          <MyProfile />
        </BrowserRouter>
      </AuthProvider>
    </ThemePreference>
  );
};
// Mock useAuth
vi.mock('../../contexts/AuthProvider', () => ({
  AuthProvider: ({ children }) => <>{children}</>,
  useAuth: vi.fn(),
}));
describe('MyProfile', () => {
  beforeAll(() => {
    Object.defineProperty(window, 'matchMedia', {
      writable: true,
      value: vi.fn().mockImplementation(query => ({
        matches: query === '(prefers-color-scheme: dark)',
        media: query,
        onchange: null,
        addEventListener: vi.fn(),
        removeEventListener: vi.fn(),
        dispatchEvent: vi.fn(),
      })),
    });
  });
  it('should show "Loading user details" when user is not set', () => {
    (useAuth as ReturnType<typeof vi.fn>).mockReturnValue({ user: null });
    renderComponent();
    expect(screen.getByText('Loading user details')).toBeInTheDocument();
  });
  it('should display user details when user is available', () => {
    (useAuth as ReturnType<typeof vi.fn>).mockReturnValue({
      user: {
        firstName: 'John',
        lastName: 'Doe',
        userName: 'jdoe',
        email: 'john.doe@example.com',
      },
      logout: vi.fn(),
    });
    renderComponent();
    expect(screen.getByText('John Doe')).toBeInTheDocument();
  });
});

Conclusion

By mocking useAuth, we can test the MyProfile component under different authentication states. Vitest makes it simple to mock hooks and verify component behavior efficiently.

Now you can confidently write tests for your Context API-powered components in React with TypeScript and Vitest!

Happy Coding!

Toggle Theme:

Our mission is to deliver high-quality web design, SEO, and IT support services in Vancouver, tailored to the unique needs of our clients. We aim to be your trusted partner, providing exceptional customer service that exceeds your expectations.

© 2023 Jazzed Technology | Vancouver Web Design, SEO & IT Support Company. All rights reserved.