reactjstypescriptasynchronousvitest

How to test useEffect with vitest


i'm building a portfolio website, so i'm mocking some requests to show that i know how to handle it.

Here's my "request":

export const getTestimonials = async (): Promise<Testimonial[]> => {
  // const response = await api.get("/testimonials");
  const mockedResponse: MockedAxiosResponse = {
    data: mockedTestimonials,
    status: 200,
    statusText: "OK",
    headers: {},
    config: {},
    request: {},
  };
  return mockedResponse.data;
};

mockedTestimonials is an array of objects.

export interface Testimonial {
  user: string;
  location: string;
  testimonial: string;
}

export const mockedTestimonials: Testimonial[] = [

In my component of testimonials, i've made an example of how i would do if the requests was real:

const Testimonials = (): JSX.Element => {
  useEffect(() => {
    console.log("testimonials fetched");
    //   getTestimonials()
    //     .then(testimonials_ => {
    //       setTestimonials(testimonials_);
    //     })
    //     .catch(err => {
    //       console.error(err);
    //     });
  }, []);

  const [testimonials, setTestimonials] =
    useState<Testimonial[]>(mockedTestimonials);

NOW THE PROBLEM

I was trying to write a test to it, so i've made a spy on useEffect:

test("Request made", async () => {
  const spy = vi.spyOn(React, "useEffect");
  await render(<Testimonials />);
  expect(spy).toHaveBeenCalled();
});

This test apparently doesn't recognize that useEffect was called, but this test works:

test("Request made", async () => {
  const spy = vi.spyOn(console, "log");
  await render(<Testimonials />);
  expect(spy).toHaveBeenCalledWith("testimonials fetched");
});

I'm trying to do the best automated test to this situation, since it's not real, i'm not testing the global fetch, the console.log is the best way i've found, but i think that is not good, i've searched for useEffect tests with vitest, but the examples are complex like real requests, custom hooks, so doesn't fit my need,can someone help how i could test if the useEffect was called, or some idea of a better test? Since then, thanks.

Tried spy and mock of vitest


Solution

  • Normally you do NOT test any methods from React directly, rather than treat a component as a black-box, where you pass some prop in, expect something out.

    However, since you aren't rendering any data from the response, we will have to do it differently -- spyon getTestimonials instead.

    // assuming `getTestimonials` is stored in `./api.tx` file
    import * Api from './api'
    
    const getTestimonialsSpy = vi.spyOn(Api, 'getTestimonialsSpy')
    
    afterEach(()=>{ vi.clearAllMocks()}) // reset mock counts
    
    test("Request made", () => {
      render(<Testimonials />);
      expect(getTestimonialsSpy).toHaveBeenCalledTimes(1);
    });
    

    The other ways is to render all the Testimonials response to the component, add the data-testid to the attribute of the children components, then do assertions with

    // assume the children component has `data-testid="testimonial"` and has 3 items
    expect(screen.getAllByTestId('testimonial')).toHaveLength(3)