In React.js with Vitest, how can I test a list that displays data from an API is rendered properly?

28 Views Asked by At

I have a list component to display data fetched from a REST API. I do the fetching in a useEffect hook with no dependency array, and set the result into my state. This is my code:

import { useEffect, useState } from 'react';
import { fetchAllMedias } from '../service';
import { MediaResponse } from '../types';
import { MediaType } from '../enums';

export default function MediaList() {
  const [medias, setMedias] = useState<MediaResponse[]>([]);

  useEffect(() => {
    const setMediasOnLoad = async () => {
      try {
        const fetchedMedias = await fetchAllMedias();
        setMedias(fetchedMedias);
      } catch (error) {
        console.error('error fetching medias:', error);
      }
    };
    void setMediasOnLoad();
  }, []);

  const getMediaTypeIcon = (mediaType: MediaType) => {
    /* returns an Icon component */
  };

  return (
    <div id='media-list'>
      {medias.map((media) => (
        <div key={media.mediaId} className='grid grid-cols-12 border-b-2 first:border-t-2 py-2 gap-4'>
          <div className='col-span-6 pl-4 flex gap-2'>
            <div className='flex items-center'>{getMediaTypeIcon(media.mediaType)}</div>
            <span>{media.title}</span>
          </div>
          <div className='border border-violet-200 bg-violet-200 rounded-md font-semibold text-violet-700 px-2'>
            {media.mediaLanguage.mediaLanguageName}
          </div>
          <div className='border border-emerald-200 bg-emerald-200 rounded-md font-semibold text-emerald-700 px-2'>
            {media.country.countryName}
          </div>
        </div>
      ))}
    </div>
  );
}

I would like to write a unit test to make sure that the list can be rendered properly, e.g. checking that the title of a movie can be shown. I have provided a mock implementation for the fetchAllMedias function, and I have asserted that the function was called. However, the list does not render in the test. This is how I have written the test:

import { render, screen, waitFor } from '@testing-library/react';
import '@testing-library/jest-dom';
import * as service from '../service';
import MediaList from '.';

const mockMedias = [
  /* mock data */
];

test('Can render media list', async () => {
  const mockFetchMedias = vi.spyOn(service, 'fetchAllMedias');
  mockFetchMedias.mockReturnValue(new Promise(() => mockMedias));

  render(<MediaList />);

  expect(mockFetchMedias).toHaveBeenCalled();
  expect(mockFetchMedias).toReturnWith(new Promise(() => mockMedias));
  await waitFor(() => {
    expect(screen.getByText(/Avengers/)).toBeInTheDocument();
  });
});

When running vitest, I get the following error:

TestingLibraryElementError: Unable to find an element with the text: /Avengers/. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.

Ignored nodes: comments, script, style
<body>
  <div>
    <div
      id="media-list"
    />
  </div>
</body>

I am new to vitest (and frontend testing in general). I have searched for a long time how to fix this issue, but I haven't been able to find others with the same problem. Any help with this will be greatly appreciated.

1

There are 1 best solutions below

0
user178456 On

Found the issue! The issue was with the line

mockFetchMedias.mockReturnValue(new Promise(() => mockMedias));.

There is another function, mockResolvedValue, that should be used instead for async functions. So I changed the above line to

mockFetchMedias.mockResolvedValue(mockMedias);

and the test is passing now!

I also moved the render function into the waitFor block, and removed the assertion outside instead:

test('Can render media list', async () => {
  const mockFetchMedias = vi.spyOn(service, 'fetchAllMedias');
  mockFetchMedias.mockResolvedValue(mockMedias);

  await waitFor(() => {
    render(<MediaList />);
  });

  expect(mockFetchMedias).toHaveBeenCalled();
  expect(mockFetchMedias).toReturnWith(mockMedias);
  expect(screen.getByText(/Avengers/)).toBeInTheDocument();
});