Mocking a module inside vi.mockReturnValueOnce in React testing

I’m creating a Shopping app with React. I have set up 3 routes (<Home/>, <ShoppingItems/>, and <Cart/>) and am trying to test the <ShoppingItems/> page. <ShoppingItems/> uses a custom hook called useItems to fetch shopping items from fakestoreAPI and display them one by one using the <ShoppingItem/> component.

Here is ShoppingItems.jsx:

// ShoppingItems.jsx

import ShoppingItem from '../components/ShoppingItem';
import useItems from '../hooks/use-items';
import styles from '../styles/ShoppingItems.module.css';

export default function ShoppingItems() {
  const { items, loading, error } = useItems();

  return (
    <main className={styles.main}>
      {loading ? (
        <p className={styles.loading}>Loading...</p>
      ) : error ? (
        <p className={styles.error}>{error}</p>
      ) : (
        <>
          <h1 className={styles.heading}>Items</h1>
          <div className={styles.items}>
            {items.map((item) => (
              <ShoppingItem
                key={item.id}
                title={item.title}
                price={item.price}
                imageURL={item.imageURL}
              />
            ))}
          </div>
        </>
      )}
    </main>
  );
}

And use-items.jsx:

// use-items.jsx

import { useEffect, useState } from 'react';
import { useOutletContext } from 'react-router-dom';

export default function useItems() {
  const [items, setItems] = useOutletContext();
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const controller = new AbortController();
    const signal = controller.signal;

    const fetchItems = async () => {
      try {
        const res = await fetch('https://fakestoreapi.com/products?limit=15', {
          signal,
        });

        if (!res.ok) {
          throw new Error(`HTTP error: Status ${res.status}`);
        }

        const data = await res.json();

        setItems(
          data.map((item) => ({
            title: item.title,
            price: item.price,
            imageURL: item.image,
            id: item.id,
          })),
        );
        setError(null);
      } catch (err) {
        setError(err.message);
        setItems([]);
      } finally {
        setLoading(false);
      }
    };

    fetchItems();

    return () => controller.abort();
  }, [setItems]);

  return { items, loading, error };
}

While I was able to test the loading and error states returned by useItems in my test file, I can’t figure out how I can test that 15 items are rendered by <ShoppingItems/>:

//ShoppingItems.test.jsx

import { createMemoryRouter } from 'react-router-dom';
import { vi } from 'vitest';
import routes from '../routes';
import { RouterProvider } from 'react-router';
import { render, screen } from '@testing-library/react';

vi.mock('../hooks/use-items.jsx', () => {
  return {
    default: vi
      .fn()
      .mockReturnValueOnce({ loading: true })
      .mockReturnValueOnce({ error: 'Error' }),
  };
});

const entry = {
  initialEntries: ['/shopping-items'],
};

it("Renders 'Loading...' when the API request is in progress", () => {
  const router = createMemoryRouter(routes, entry);

  render(<RouterProvider router={router} />);

  expect(screen.getByText('Loading...')).toBeInTheDocument();
});

it("Renders 'Error' when there is an error", () => {
  const router = createMemoryRouter(routes, entry);

  render(<RouterProvider router={router} />);

  expect(screen.getByText('Error')).toBeInTheDocument();
});

it('Renders 15 shopping items', () => {
  const router = createMemoryRouter(routes, entry);

  render(<RouterProvider router={router} />);
});

As you can see, I mock use-items.jsx with vi.mock and test those 2 states with vi.mockReturnValueOnce. How can I test that items are rendered in correct amount using this approach? Or is there a better approach in my case?

What I’ve tried:

I was planning to return an array of 15 div elements with the text “fetched item” like this:

vi.mock('../hooks/use-items.jsx', () => {
  return {
    default: vi
      .fn()
      .mockReturnValueOnce({ loading: true })
      .mockReturnValueOnce({ error: 'Error' })
      .mockReturnValueOnce({items: []}), // 15 div elements
  };
});

Then I would be able to test their presence using expect(screen.getAllByText('fetched item').length).toMatch(15).

However, I don’t know how to accomplish this without manually hardcoding those div elements. Also, I want to mock the ShoppingItem.jsx module and make it return a value once with vi.mockReturnValueOnce so that I can test in my 3rd test, if that makes any sense. Feel free to correct my approach since I’m new to React testing.