React Query Test with MSW - renderHook result.current always returns null
rostgoat opened this issue · 1 comments
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.
followed your original tutorial
App.tsx
const App = () => {
const {
isLoading,
data,
error
} = useFetchAccounts();
if (isLoading) return "Loading...";
if (error) return "An error has occurred: " + error;
return (
<div>
{data?.map(item => (
<div key={item.id}>{item.value}</div>
))}
</div>
);
};
App.spec.tsx
const testQueryClient = new QueryClient({
defaultOptions: {
queries: {
retry: false
}
}
});
testQueryClient.setQueryDefaults(["accounts"], { retry: 5 });
describe("Accounts", () => {
it("renders accounts", async () => {
const { result } = renderHook(() => useFetchAccounts(), {
wrapper: () => (
<QueryClientProvider client={testQueryClient}>
<App />
</QueryClientProvider>
)
});
console.log("result", result);
await waitFor(() => expect(result.current.isSuccess).toBe(true));
});
});
hooks.tsx
import { useQuery } from "@tanstack/react-query";
type FormattedAccount = {
id: number;
label: string;
};
const fetchAccount = async () => {
const res = await fetch("/api/accounts");
const accounts = await res.json();
console.log("accounts", accounts);
const formattedAccounts: FormattedAccount[] = [...accounts]?.map(
(option) => ({
id: option.id,
label: option.name,
})
);
console.log("formattedAccounts", formattedAccounts);
return formattedAccounts;
};
export const useFetchAccounts = () => {
return useQuery({
queryKey: ["accounts"],
queryFn: () => fetchAccount()
});
};
server.ts
import { setupServer } from "msw/node";
import { rest } from "msw";
export const handlers = [
rest.get("/api/accounts", (req, res, ctx) => {
const rres = res(ctx.status(200), ctx.json([{ id: 1, name: "Account 1" }]));
console.log("rres", rres);
return rres;
})
];
export const server = setupServer(...handlers);
jest.config.js
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
preset: "ts-jest",
collectCoverage: true,
collectCoverageFrom: ["src/**/*.spec.{ts, tsx}"],
coverageDirectory: "coverage",
testEnvironment: "jsdom",
setupFilesAfterEnv: ["<rootDir>/jest.setup.ts"],
transform: {
"^.+\\.(ts|tsx)?$": "ts-jest"
}
};
jest.setup.ts
import "@testing-library/jest-dom";
import "whatwg-fetch";
import { server } from "./server";
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
export default global.matchMedia =
global.matchMedia ||
function (query) {
return {
matches: false,
media: query,
onchange: null,
addListener: jest.fn(), // deprecated
removeListener: jest.fn(), // deprecated
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn()
};
};
you're mixing things here a bit. You either want to test a component, or the hook.
If you test a component, test a real component (like App.tsx
), and as a wrapper
, pass in the QueryClient only. Then, your assertions should check for html output:
testing-react-query/src/tests/App.test.tsx
Lines 6 to 11 in 20e35ed
if you want to test the hook, use renderHook
, but then you don't need a DummyComponent
- just the hook with the Provider as well:
btw, result.current
is null
because wrapper
is a react component that gets children
passed, and you need to render them, which you don't:
const { result } = renderHook(() => useFetchAccounts(), {
- wrapper: () => (
+ wrapper: ({ children }) => (
<QueryClientProvider client={testQueryClient}>
- <DummyComponent />
+ {children}
</QueryClientProvider>
)
});
this basically gives you the "testing the hook" solution, but I'd prefer testing a complete component instead.