6

I have a Dashboard component which contains a child component, e.g. 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;
        },

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

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,

1 Answer 1

5

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, e.g.

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>,
);
Sign up to request clarification or add additional context in comments.

3 Comments

Tx for the answer, I added some extra information for context.
Seems to be a TypeScript related issue, try to import useFetch in test like I do : import * as FooModule from 'path/to/file' maybe ?
Tx, I updated it as such. My child test is passing, but my Dashboard test is still failing with the original error: TypeError: queryClient.defaultQueryObserverOptions is not a function

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.