React-Query - модульный тест с библиотекой react-testing-library

У меня есть компонент Dashboard, который содержит дочерний компонент, например. Child, который использует ответный запрос.

У меня есть существующий модульный тест для компонента Dashboard, который начал давать сбой, ошибка:

TypeError: queryClient.defaultQueryObserverOptions is not a function

  38 |     const { locale } = React.useContext(LocaleStateContext);
  39 |     const options = getOptions(locale);
> 40 |     return useQuery(
     |            ^
  41 |         rqKey,
  42 |         async () => {
  43 |             const result = await window.fetch(url, options);

Фрагмент из теста:

const queryClient = new QueryClient();
const { getByTestId, getByRole } = render(
    <IntlProvider locale="en" messages={messages}>
        <QueryClientProvider client={queryClient}>
            <Dashboard />
        </QueryClientProvider>
    </IntlProvider>,
);

Прочитал документацию по тестированию:

https://react-query.tanstack.com/guides/testing#our-first-test

Но я не хочу обязательно использовать renderHook, поскольку меня не интересует результат.

РЕДАКТИРОВАТЬ:

Компонент Child использует функцию:

export function usePosts({ rqKey, url, extraConfig }: CallApiProps) {
    const { locale } = React.useContext(LocaleStateContext);
    const options = getOptions(locale);
    return useQuery(
        rqKey,
        async () => {
            const result = await window.fetch(url, options);
            const data = await result.json();
            return data;
        },
        extraConfig,
    );
}

Что называется так:

const { data, error, isFetching, isError } = usePosts({
        rqKey,
        url,
        extraConfig,
    });

Исходя из вашего ответа, я должен создать отдельную функцию:

async () => {
            const result = await window.fetch(url, options);
            const data = await result.json();
            return data;
        },

e.g.

export async function usePosts({ rqKey, url, extraConfig }: CallApiProps) {
    const { locale } = React.useContext(LocaleStateContext);
    const options = getOptions(locale);
    return useQuery(
        rqKey,
        await getFoos(url, options),
        extraConfig,
    );
}

А потом поиздевайтесь над этим в тесте.

Если я это сделаю, то как тогда я получу доступ к: error, isFetching, isError

Поскольку usePosts() теперь вернет Promise<QueryObserverResult<unknown, unknown>>

РЕДАКТИРОВАТЬ 2:

Я попытался упростить свой код:

export async function useFetch({ queryKey }: any) {
    const [_key, { url, options }] = queryKey;
    const res = await window.fetch(url, options);
    return await res.json();
}

Что затем используется как:

const { isLoading, error, data, isError } = useQuery(
    [rqKey, { url, options }],
    useFetch,
    extraConfig,
);

Все работает.

Затем в тесте Dashboard я делаю следующее:

import * as useFetch from ".";

а также

jest.spyOn(useFetch, "useFetch").mockResolvedValue(["asdf", "asdf"]);

а также

render(
        <IntlProvider locale="en" messages={messages}>
            <QueryClientProvider client={queryClient}>
                <Dashboard />
            </QueryClientProvider>
        </IntlProvider>,
    );

Что затем возвращается:

TypeError: queryClient.defaultQueryObserverOptions is not a function

      78 |     const { locale } = React.useContext(LocaleStateContext);
      79 |     const options = getOptions(locale);
    > 80 |     const { isLoading, error, data, isError } = useQuery(
         |                                                 ^
      81 |         [rqKey, { url, options }],
      82 |         useFetch,
      83 |         extraConfig,

person Robert Stevens    schedule 14.01.2021    source источник


Ответы (1)


На упомянутой вами странице документации объясняется, как тестировать пользовательские хуки, основанные на React Query. Вы используете собственные хуки на основе React Query или просто хотите протестировать компоненты, использующие useQuery (хук, предоставляемый React Query)?

Если вы просто хотите протестировать дочерний элемент, который использует useQuery, вы должны имитировать свои функции запроса (функции, возвращающие обещания, используемые в качестве вторых аргументов для useQuery) и визуализировать тестируемый компонент без какого-либо поставщика.

Например, в "Ребенок" у вас есть

const foo = useQuery('key', getFoos, { // additional config here });
// foo is a QueryResult object (https://react-query.tanstack.com/reference/useQuery)
// so your usePost function will return a QueryResult as well
// foo.data holds the query results (or undefined)
// you can access to foo.error, foo.isFetching, foo.status...
// also note that extra parameter to be passed to your async function 
// should be part of the request key. Key should be an array :
// useQuery(['key', params], getFoos, { // additional config });
// so params object props will be passed as parameters for getFoos fucntion
// see https://react-query.tanstack.com/guides/query-keys#array-keys

... и getFoos определен в path/to/file/defining/getFoos.ts как

const getFoos = async (): Promise<string[]> => await fetch(...);

... тогда в Child.test.tsx вы можете сделать

import * as FooModule from 'path/to/file/defining/getFoos';

// this line could be at the top of file or in a particular test()
jest.spyOn(FooModule, 'getFoos').mockResolvedValue(['mocked', 'foos']);

// now in your Child tests you'll always get ['mocked', 'foos']
// through useQuery (in foo.data), but you'll still have to use https://testing-library.com/docs/dom-testing-library/api-async/#waitfor (mocked but still async)
// No need for QueryClientProvider in this case, just render <Child />

Отвечать:

Хотя приведенный выше ответ помог мне в правильном направлении, основная проблема заключалась в том, что я использовал mockImplementation для предоставления контекста, который затем сделал контекст, предоставленный QueryClientProvider, бесполезным, например

jest.spyOn(React, "useContext").mockImplementation(() => ({
    ...
}));

В итоге я удалил mockImplementation и добавил в свой UserStateContext.Provider вместе с QueryClientProvider, и проблема была решена:

render(
    <IntlProvider locale="en" messages={messages}>
        <UserStateContext.Provider value={value}>
            <QueryClientProvider client={queryClient}>
                <Dashboard />
            </QueryClientProvider>
        </UserStateContext.Provider>
    </IntlProvider>,
);
person Florian Motteau    schedule 15.01.2021
comment
Tx за ответ, я добавил дополнительную информацию для контекста. - person Robert Stevens; 18.01.2021
comment
Я обновил свой вопрос, см. ИЗМЕНИТЬ 2. - person Robert Stevens; 18.01.2021
comment
Кажется, это проблема, связанная с TypeScript, попробуйте импортировать useFetch в тесте, как я: возможно, импортировать * как FooModule из пути / в / файл? - person Florian Motteau; 18.01.2021
comment
Тх, обновил как таковой. Мой дочерний тест проходит, но мой тест на панели инструментов по-прежнему не работает с исходной ошибкой: TypeError: queryClient.defaultQueryObserverOptions не является функцией - person Robert Stevens; 18.01.2021