reactjstypescriptjestjsreact-testing-libraryswr

What should i test in this custom hook that is wrapping useSWR?


I have created a custom hook called useCity. It is wrapping an API call made using useSWR.

Here is the code for hook:

import useSWR from 'swr';

import { City } from '../interfaces';
import { BASE_URL } from '../../config';

interface CitiesResponse {
  data?: {
    records: {
      fields: {
        city: string;
        accentcity: string;
      }
    }[]
  },
  error?: {
    message: string;
  }
};

interface Props {
  start?: number;
  rows: number;
  query?: string;
  sort?: 'population';
  exclude?: string[];
}

const useCity = ({ start = 0, rows, query, sort, exclude }: Props) => {
  const params = [`start=${start}`, `rows=${rows}`];
  if (query) params.push(`q=${query}`);
  if (sort) params.push(`sort=${sort}`);
  if (exclude && exclude.length > 0) params.push(...exclude.map(city => `exclude.city=${city}`))

  const { data, error }: CitiesResponse = useSWR(
    `${BASE_URL.CITIES_SERVICE}?dataset=worldcitiespop&facet=city&${params.join('&')}`,
    { revalidateOnFocus: false,  }
  );

  const cities: City[] = data?.records.map(record => ({
    name: record.fields.city,
    title: record.fields.accentcity,
  })) || [];

  return {
    cities,
    loading: !error && !data,
    error,
  };
};

export default useCity;

Now, I need to test the hook. So, I tried using msw and @testing-library/react-hooks.

Here is my try:

const server = setupServer(
  rest.get(BASE_URL.CITIES_SERVICE, (req, res, ctx) => {
    const start = req.url.searchParams.get('start');
    const rows = req.url.searchParams.get('rows');
    const query = req.url.searchParams.get('query');
    const sort = req.url.searchParams.get('sort');
    const exclude = req.url.searchParams.getAll('exclude.city');

    const getReturnVal: () => DatabaseCity[] = () => {
      // i will write some code that assumes what server will return
    };


    return res(
      ctx.status(200),
      ctx.json({
        records: getReturnVal(),
      }),
    );
  }),
  ...fallbackHandlers,
);

beforeAll(() => server.listen());
afterEach(() => {
  server.resetHandlers();
  cache.clear();
});
afterAll(() => server.close());


it('should return number of cities equal to passed in rows', async () => {
  const wrapper = ({ children } : { children: ReactNode }) => (
    <SWRConfig value={{ dedupingInterval: 0 }}>
      {children}
    </SWRConfig>
  );

  const { result, waitForNextUpdate, } = renderHook(() => useCity({ rows: 2 }), { wrapper });
  const { cities:_cities, loading:_loading, error:_error } = result.current;
  expect(_cities).toHaveLength(0);
  
  await waitForNextUpdate();
  
  const { cities, loading, error } = result.current;
  expect(cities).toHaveLength(2);
});

I assme that the test case will pass once I implement the mock function.

But I don't know if this is the right approach to test such a hook. I am a frontend developer, is this my responsibility to test that API call?

I am new to writing test cases that involves API calls. Am I going in the right direction? I don't know what this kind of tests are called. If someone can tell me the kind of the test I am perfoming, then it will help me google for the solutions instead of wasting other developer's time to answer my questions.


Solution

  • Looks like you are on the right track.

    Your useCity hook does basically 2 things that you can validate in tests:

    1. builds an url
    2. converts the cities to another format

    You can validate useSWR is called with the correct url by using a spy:

    import * as SWR from 'swr';
    
    jest.spyOn(SWR, 'default'); // write this line before rendering the hook.
    expect(SWR.default).toHaveBeenCalledWith(expectedUrl, {}); // pass any options that were passed in actual object
    

    You can validate useCities returns correct cities by

    const { cities } = result.current;
    expect(cities).toEqual(expectedCities);
    

    I am a frontend developer, is this my responsibility to test that API call?

    I think that is up to you to find the answer. I personally see as my responsibility to test any code that I write--that of course is not a dogma and is context sensitive.

    I don't know what this kind of tests are called. If someone can tell me the kind of the test I am perfoming, then it will help me google for the solutions

    There might not be a clear answer for this. Some people would call it unit testing (since useCities is a "unit"). Others might call it integration testing (since you test useCities and useSWR in "integration").

    Your best bet would be to google things like "how to test react hooks" or "how to test react components". The RTL docs are a good place to start.


    Extra notes

    I personally almost never test hooks in isolation. I find it easier and more intuitive to write integration tests for the components that use the hooks.

    However, if your hook is going to be used by other projects, I think it makes sense testing them in isolation, like you are doing here.