T
TanStack3y ago
ratty-blush

React Query Test with MSW - renderHook `current` result always returns null

I am trying to test my react query hook using msw but renderHook's result's current value is always null. Why? Note: the reason fetch does not include a URL is because this is a Rails Propshaft app and React and Rails apps share the same port. In the real app, everything works and the hook returns data just fine but not in the test. [tutorial I followed][1] [reproducible sandbox][2] spec example
const testQueryClient = new QueryClient({
defaultOptions: {
queries: {
retry: false
}
}
});

const DummyComponent = () => {
const { isLoading, data, error } = useFetchAccounts();

console.log("isLoading", isLoading);
if (isLoading) return "Loading...";

if (error) return "An error has occurred: " + error;
console.log("data", data);

return (
<div>
{data?.map((item) => (
<div key={item.id}>{item.value}</div>
))}
</div>
);
};

describe("Accounts", () => {
it("renders accounts", async () => {
const { result } = renderHook(() => useFetchAccounts(), {
wrapper: () => (
<QueryClientProvider client={testQueryClient}>
<DummyComponent />
</QueryClientProvider>
)
});

console.log("result", result);

await waitFor(() => expect(result.current.data).toBe(true));
});
});
const testQueryClient = new QueryClient({
defaultOptions: {
queries: {
retry: false
}
}
});

const DummyComponent = () => {
const { isLoading, data, error } = useFetchAccounts();

console.log("isLoading", isLoading);
if (isLoading) return "Loading...";

if (error) return "An error has occurred: " + error;
console.log("data", data);

return (
<div>
{data?.map((item) => (
<div key={item.id}>{item.value}</div>
))}
</div>
);
};

describe("Accounts", () => {
it("renders accounts", async () => {
const { result } = renderHook(() => useFetchAccounts(), {
wrapper: () => (
<QueryClientProvider client={testQueryClient}>
<DummyComponent />
</QueryClientProvider>
)
});

console.log("result", result);

await waitFor(() => expect(result.current.data).toBe(true));
});
});
spec error:
Cannot read properties of null (reading 'data')
Cannot read properties of null (reading 'data')
[1]: https://tkdodo.eu/blog/testing-react-query [2]: https://codesandbox.io/s/blissful-dewdney-r6k4mm?file=/src/App.spec.tsx
Testing React Query
Let's take a look at how to efficiently test custom useQuery hooks and components using them.
7 Replies
ratty-blush
ratty-blushOP3y ago
tried using nock instead of msw and I face the same issue. the following is always what I see when I log isLoading and data in from the hook in the spec
isLoading true
data undefined
isLoading true
data undefined
more debug logs:
isSuccess false at DummyComponent (app/javascript/shared/hooks/use-fetch-accounts.spec.tsx:45:10)
isLoading true at DummyComponent (app/javascript/shared/hooks/use-fetch-accounts.spec.tsx:46:10)
data undefined at DummyComponent (app/javascript/shared/hooks/use-fetch-accounts.spec.tsx:47:10)
error null at DummyComponent (app/javascript/shared/hooks/use-fetch-accounts.spec.tsx:48:10)
isError: false at DummyComponent (app/javascript/shared/hooks/use-fetch-accounts.spec.tsx:49:10)
isFetched: false at DummyComponent (app/javascript/shared/hooks/use-fetch-accounts.spec.tsx:50:10)
isPaused: false at DummyComponent (app/javascript/shared/hooks/use-fetch-accounts.spec.tsx:51:10)
isPending: true at DummyComponent (app/javascript/shared/hooks/use-fetch-accounts.spec.tsx:52:10)
isRefetchError: false at DummyComponent (app/javascript/shared/hooks/use-fetch-accounts.spec.tsx:53:10)
isLoadingError: false at DummyComponent (app/javascript/shared/hooks/use-fetch-accounts.spec.tsx:54:10)
status: pending at DummyComponent (app/javascript/shared/hooks/use-fetch-accounts.spec.tsx:55:10)
isSuccess false at DummyComponent (app/javascript/shared/hooks/use-fetch-accounts.spec.tsx:45:10)
isLoading true at DummyComponent (app/javascript/shared/hooks/use-fetch-accounts.spec.tsx:46:10)
data undefined at DummyComponent (app/javascript/shared/hooks/use-fetch-accounts.spec.tsx:47:10)
error null at DummyComponent (app/javascript/shared/hooks/use-fetch-accounts.spec.tsx:48:10)
isError: false at DummyComponent (app/javascript/shared/hooks/use-fetch-accounts.spec.tsx:49:10)
isFetched: false at DummyComponent (app/javascript/shared/hooks/use-fetch-accounts.spec.tsx:50:10)
isPaused: false at DummyComponent (app/javascript/shared/hooks/use-fetch-accounts.spec.tsx:51:10)
isPending: true at DummyComponent (app/javascript/shared/hooks/use-fetch-accounts.spec.tsx:52:10)
isRefetchError: false at DummyComponent (app/javascript/shared/hooks/use-fetch-accounts.spec.tsx:53:10)
isLoadingError: false at DummyComponent (app/javascript/shared/hooks/use-fetch-accounts.spec.tsx:54:10)
status: pending at DummyComponent (app/javascript/shared/hooks/use-fetch-accounts.spec.tsx:55:10)
here at Object.fetchAccounts [as queryFn] (app/javascript/shared/hooks/use-fetch-accounts.ts:11:10)
url /api/accounts at request (app/javascript/shared/request.ts:50:11)
isSuccess false at DummyComponent (app/javascript/shared/hooks/use-fetch-accounts.spec.tsx:45:10)
isLoading true at DummyComponent (app/javascript/shared/hooks/use-fetch-accounts.spec.tsx:46:10)
data undefined at DummyComponent (app/javascript/shared/hooks/use-fetch-accounts.spec.tsx:47:10)
error null at DummyComponent (app/javascript/shared/hooks/use-fetch-accounts.spec.tsx:48:10)
isError: false at DummyComponent (app/javascript/shared/hooks/use-fetch-accounts.spec.tsx:49:10)
isFetched: false at DummyComponent (app/javascript/shared/hooks/use-fetch-accounts.spec.tsx:50:10)
isPaused: false at DummyComponent (app/javascript/shared/hooks/use-fetch-accounts.spec.tsx:51:10)
isPending: true at DummyComponent (app/javascript/shared/hooks/use-fetch-accounts.spec.tsx:52:10)
isRefetchError: false at DummyComponent (app/javascript/shared/hooks/use-fetch-accounts.spec.tsx:53:10)
isLoadingError: false at DummyComponent (app/javascript/shared/hooks/use-fetch-accounts.spec.tsx:54:10)
status: pending at DummyComponent (app/javascript/shared/hooks/use-fetch-accounts.spec.tsx:55:10)
result { current: null } at timeout (app/javascript/shared/hooks/use-fetch-accounts.spec.tsx:102:31)
res Response {
type: 'default',
status: 200,
ok: true,
statusText: 'OK',
headers: Headers {
map: { 'x-powered-by': 'msw', 'content-type': 'application/json' }
},
url: 'http://localhost/api/accounts',
bodyUsed: false,
_bodyInit: '[{"id":1,"name":"No Degree"}]',
_bodyText: '[{"id":1,"name":"No Degree"}]'
} at request (app/javascript/shared/request.ts:58:11)
here at Object.fetchAccounts [as queryFn] (app/javascript/shared/hooks/use-fetch-accounts.ts:11:10)
url /api/accounts at request (app/javascript/shared/request.ts:50:11)
isSuccess false at DummyComponent (app/javascript/shared/hooks/use-fetch-accounts.spec.tsx:45:10)
isLoading true at DummyComponent (app/javascript/shared/hooks/use-fetch-accounts.spec.tsx:46:10)
data undefined at DummyComponent (app/javascript/shared/hooks/use-fetch-accounts.spec.tsx:47:10)
error null at DummyComponent (app/javascript/shared/hooks/use-fetch-accounts.spec.tsx:48:10)
isError: false at DummyComponent (app/javascript/shared/hooks/use-fetch-accounts.spec.tsx:49:10)
isFetched: false at DummyComponent (app/javascript/shared/hooks/use-fetch-accounts.spec.tsx:50:10)
isPaused: false at DummyComponent (app/javascript/shared/hooks/use-fetch-accounts.spec.tsx:51:10)
isPending: true at DummyComponent (app/javascript/shared/hooks/use-fetch-accounts.spec.tsx:52:10)
isRefetchError: false at DummyComponent (app/javascript/shared/hooks/use-fetch-accounts.spec.tsx:53:10)
isLoadingError: false at DummyComponent (app/javascript/shared/hooks/use-fetch-accounts.spec.tsx:54:10)
status: pending at DummyComponent (app/javascript/shared/hooks/use-fetch-accounts.spec.tsx:55:10)
result { current: null } at timeout (app/javascript/shared/hooks/use-fetch-accounts.spec.tsx:102:31)
res Response {
type: 'default',
status: 200,
ok: true,
statusText: 'OK',
headers: Headers {
map: { 'x-powered-by': 'msw', 'content-type': 'application/json' }
},
url: 'http://localhost/api/accounts',
bodyUsed: false,
_bodyInit: '[{"id":1,"name":"No Degree"}]',
_bodyText: '[{"id":1,"name":"No Degree"}]'
} at request (app/javascript/shared/request.ts:58:11)
accounts [ { id: 1, name: 'No Degree' } ] at Object.fetchAccounts [as queryFn] (app/javascript/shared/hooks/use-fetch-accounts.ts:13:10)
formattedAccounts [ { id: 1, label: 'No Degree', value: 'no_degree' } ] at Object.fetchAccounts [as queryFn] (app/javascript/shared/hooks/use-fetch-accounts.ts:19:10)
accounts [ { id: 1, name: 'No Degree' } ] at Object.fetchAccounts [as queryFn] (app/javascript/shared/hooks/use-fetch-accounts.ts:13:10)
formattedAccounts [ { id: 1, label: 'No Degree', value: 'no_degree' } ] at Object.fetchAccounts [as queryFn] (app/javascript/shared/hooks/use-fetch-accounts.ts:19:10)
tried the following react-query versions:
^5.0.0-beta.2
4.32.0
^5.0.0-beta.2
4.32.0
the strange thing is that I can see formattedAccounts being returned in the hook but data is undefined in DummyComponent 🤔
ratty-blush
ratty-blushOP3y ago
The example repo (https://github.com/TkDodo/testing-react-query) has the following helper, which made the tests pass
function createWrapperOG() {
const testQueryClient = createTestQueryClient()
return ({ children }: {children: React.ReactNode}) => (
<QueryClientProvider client={testQueryClient}>{children}</QueryClientProvider>
)
}
function createWrapperOG() {
const testQueryClient = createTestQueryClient()
return ({ children }: {children: React.ReactNode}) => (
<QueryClientProvider client={testQueryClient}>{children}</QueryClientProvider>
)
}
vs the helper I was trying to use
const CreateWrapper = ({children}: {children: React.ReactNode}) => {
const testQueryClient = createTestQueryClient()

return (
<QueryClientProvider client={testQueryClient}>{children}</QueryClientProvider>
)
}
const CreateWrapper = ({children}: {children: React.ReactNode}) => {
const testQueryClient = createTestQueryClient()

return (
<QueryClientProvider client={testQueryClient}>{children}</QueryClientProvider>
)
}
Why does the original have to return a function that returns a component?
GitHub
GitHub - TkDodo/testing-react-query
Contribute to TkDodo/testing-react-query development by creating an account on GitHub.
xenophobic-harlequin
xenophobic-harlequin3y ago
because wrapper passed to render from testing-libary is a function and not a component 🤷‍♂️
xenophobic-harlequin
xenophobic-harlequin3y ago
oh, I stand corrected: https://testing-library.com/docs/react-testing-library/api/#wrapper wrapper is a component 🤔
API | Testing Library
React Testing Library re-exports everything from DOM Testing Library as well
xenophobic-harlequin
xenophobic-harlequin3y ago
possibly, the wrapper gets re-rendered, so you need to make the QueryClient stable ? https://tkdodo.eu/blog/react-query-fa-qs#2-the-queryclient-is-not-stable
React Query FAQs
Answering the most frequently asked React Query questions
xenophobic-harlequin
xenophobic-harlequin3y ago
my wrapper kinda side-steps this by having the queryClient in the closure
ratty-blush
ratty-blushOP3y ago
wild..thanks for the refs! really easy thing to miss I think tho. Until I cloned your repo and ran it locally, I was stuck on this, probably for a good 24hr

Did you find this page helpful?