2

我创建了一个名为 useCity 的自定义钩子。它包装了使用 useSWR 进行的 API 调用。

这是钩子的代码:

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;

现在,我需要测试钩子。所以,我尝试使用mswand @testing-library/react-hooks

这是我的尝试:

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);
});

我认为一旦我实现了模拟功能,测试用例就会通过。

但我不知道这是否是测试这种钩子的正确方法。我是前端开发人员,这是我测试 API 调用的责任吗?

我是编写涉及 API 调用的测试用例的新手。我是否朝着正确的方向前进?我不知道这种测试叫什么。如果有人能告诉我我正在执行的测试类型,那么它将帮助我在谷歌上搜索解决方案,而不是浪费其他开发人员的时间来回答我的问题。

4

1 回答 1

2

看起来你在正确的轨道上。

你的useCity钩子基本上做了两件事,你可以在测试中验证:

  1. 建立一个网址
  2. 将城市转换为另一种格式

您可以useSWR使用 spy 验证是否使用正确的 url 调用:

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

您可以通过以下方式验证useCities返回正确的城市

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

我是前端开发人员,这是我测试 API 调用的责任吗?

我认为这取决于您找到答案。我个人认为测试我编写的任何代码是我的责任——这当然不是教条,而且是上下文相关的。

我不知道这种测试叫什么。如果有人可以告诉我我正在执行的测试类型,那么它将帮助我在谷歌上搜索解决方案

对此可能没有明确的答案。有些人会称之为单元测试(因为useCities是“单元”)。其他人可能将其称为集成测试(因为您测试useCitiesuseSWR处于“集成”状态)。

你最好的选择是谷歌诸如“如何测试反应钩子”或“如何测试反应组件”之类的东西。RTL 文档是一个很好的起点。


额外说明

我个人几乎从不单独测试钩子。我发现为使用钩子的组件编写集成测试更容易、更直观。

但是,如果您的钩子将被其他项目使用,我认为单独测试它们是有意义的,就像您在这里所做的那样。

于 2020-11-20T08:40:11.817 回答