简体   繁体   中英

React-Query - Unit Test with react-testing-library

I have a Dashboard component which contains a child component, eg Child which makes uses of react-query.

I have an existing unit test for the Dashboard component which started to fail, error being:

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

Snippet from the test:

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

I read the documentation about testing:

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

But I don't want to necessarily want to use renderHook as I am not interested in the result.

EDIT:

The Child component is using a function:

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

Which is called as such:

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

From your answer, I should be creating a separate function of:

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

eg

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

And then mock it in the test.

If I do that how would I then get access to: error, isFetching, isError

As usePosts() will now return a Promise<QueryObserverResult<unknown, unknown>>

EDIT 2:

I tried simplifying my code:

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

Which is then used as:

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

All works.

In the Dashboard test, I then do the following:

import * as useFetch from ".";

and

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

and

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

Which then returns:

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,

The doc page you mention explains how to test custom hooks relying on React Query. Are you using custom hooks based on React Query or do you just want to test components that use useQuery (hook provided by React Query)?

If you just want to test Child wich uses useQuery, you should mock your "request functions" (functions that return Promises, used as second arguments for useQuery ), and render your component under test without any provider.

For example, say in Child you have

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

...and getFoos is defined in path/to/file/defining/getFoos.ts as

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

...then in Child.test.tsx you can do

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 />

Answer:

Although the above answer helped me in the right direction, the underlying issue was that I was using mockImplementation to provide context which then rendered the context given by QueryClientProvider useless, eg

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

I ended up removing the mockImplementation and added in my UserStateContext.Provider along side the QueryClientProvider and problem solved:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM