We are currently using react-query (which is totally awesome) and we find it hard to write thats for code that uses it. There are no examples or guidelines . The options are to either mock http level or mock the function passed touseQuery. It would be helpful to have a utility method such as:
onQuery('resource', () => //return mock data) that will be used useQuery instead of the queryFn to retrieve data.
Example:
//COMPONENT
function Comp () {
const {data} = useQuery('resources' , () => {....});
return <div>{data}</div>
}
//TEST
it('some test', () => {
onQuery('resource', () => //return mock data)`
//render component
//assert
});
I'd love to discussed and create a PR for it
Another (simpler) could be to allow overriding this behavior: https://github.com/tannerlinsley/react-query/blob/master/src/index.js#L414
This would allow wrapping a component with another component which sets the "mock data". For example:
```
/// TestedComponent.tsx
function TestedComponent() {
const {data} = useQuery('products', () => //make http call);
return
///TEST
it('blah', () => {
render(
function Comp() {
useQuery('products', () => //return mock data);
return
}
)
});
@borislit I have been using sinon to mock out useQuery entirely. Not saying this is the right way to test components that use reactQuery, but it's worked for me so far.
import sinon from 'sinon';
import * as reactQuery from 'react-query';
describe('test', () => {
const sandbox = sinon.createSandbox();
it('should run the test', () => {
sandbox.stub(reactQuery, 'useQuery').returns({ data: null, isLoading: false, error: null });
// etc...
})
});
@cschwebk while its possible, I dont think its optimal. You are basically mocking out a code you dont own, which can potentially lead to false assumptions about how it works, aswell as false positives (in case of an upgrade.
@borislit In our unit tests we generally only care about validating the code which we own and use package-lock.json with manually managed package upgrades to mitigate any false positives from package version upgrades.
If you're concerned with your test also fully testing useQuery, you can stub out the method you're providing to useQuery to make the request and provide your mock data there.
I can only recommend that you use a library like @testing-library/react and only test the public api/output of react-query. React-Query will be difficult to mock outside of returning synchronous data, so I would advise against it, and would instead urge you to simply mock your query functions and variables to react-query.
Hi,
(I write this for the record and maybe to help other people)
Here an example of what i did to test a custom hook wrapping useQuery:
import { renderHook, act } from '@testing-library/react-hooks';
import nock from 'nock';
const isQuerySuccessful = (queryResult) => queryResult && queryResult.status === 'success';
const waitQueryIsSuccessful = (renderHookResult) => {
return renderHookResult.wait(() => isQuerySuccessful(renderHookResult.result.current), { timeout: 1000 });
};
it('should return data when API responds 200', async () => {
let actualData = null;
nock('http://bff.test/api')
.get('/data')
.reply(200, yourData);
await act(async () => {
const renderHookResult = renderHook(() => useMyUseQueryWrap('some', 'params'));
const { result, unmount } = renderHookResult;
await waitQueryIsSuccessful(renderHookResult);
actualData = result.current.data;
unmount(); // This seems necessary to avoid some 'act' warnings
});
// Here the expectation for actualData
});
I want to add some additional ideas for testing react-query hooks using nock and @testing-library/react-hooks.
Here's an example:
import {renderHook} from '@testing-library/react-hooks';
import nock from 'nock';
it('fetches data', async () => {
// If you don't scope your nocks to a variable, your tests will collide.
// Not using a variable allows you to interface with a global instance.
const scope = nock('https://test.com/api')
.get('/whatevs')
.reply(200, {data: {test: 'works'}});
const {result, waitForNextUpdate} = renderHook(() =>
useMyQuery()
);
await waitForNextUpdate();
expect(result.current.data).toEqual({test: 'works'});
expect(scope.isDone()).toBe(true);
});
Using v1.5.10 of react-query, the renderHook call works great - but if I update to >v2, the tests all fail with:
Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.Error: Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.
at mapper (node_modules/jest-jasmine2/build/queueRunner.js:25:45)
Hi there!
Trying to get it working using the above examples as inspiration, however my query is not getting out of loading state, somebody knows what's wrong here?
import { renderHook } from '@testing-library/react-hooks'
import { useMyCustomQuery } from './use-my-custom-query';
import nock from 'nock';
describe('useShopReviews', () => {
it('fetches data', async () => {
const scope = nock("https://api.foo.com")
.get('/bar')
.reply(200, { data: { test: 'works' } });
const {result, waitForNextUpdate} = renderHook(() =>
useMyCustomQuery('foo')
);
expect(result.current.isLoading).toBe(true);
await waitForNextUpdate();
console.log(result.current.status); // loading
await waitForNextUpdate();
console.log(result.current.status); // still loading
expect(result.current.data).toEqual({ test: 'works' }); // fails, data is undefined
expect(scope.isDone()).toBe(true);
});
});
@tannerlinsley same issue here using mock-service-worker in this codesandbox - https://codesandbox.io/s/react-query-msw-test-s5hre
I stumbled into the "not working under 2.x" issue - after a while, I ended up bumping react-hooks-testing-library to 3.4.2 and using https://react-hooks-testing-library.com/reference/api#waitfor with an interval, and patterns similar to the above now work.
The interval param allows waits to check callbacks even if another hook render has not occurred, which seems to fix things up here.
... and patterns similar to the above now work.
Please could you show us the patterns that work? 馃檹
I don't have but it was fairly simple. Previously this used wait w/ react-hooks-testing-library 3.3 (which didn't yet support the interval) and was getting async timeouts all the time on the wait. I don't know enough about the internals here but I think wait/waitFor w/o an interval param will only pick up changes that happen when the hook rerenders. Interval seems to consistently wait properly for me.
import { renderHook } from '@testing-library/react-hooks';
....
const { result, waitFor } = renderHook(() =>
useMyCustomQueryHook(),
);
await waitFor(() => result.current.isSuccess, { interval: 1000 });
same
Does anybody have working example for testing component that uses useQuery, without any custom hooks headache? Just spent about half of the day figuring out a solution, but can't get out of being stuck with infinite "Loading" state in tests.
馃檹
the official documentation has a working example
https://react-query.tanstack.com/docs/guides/testing
Official documentation focuses only on custom hooks, which in my opinion is big overcomplication - core usage (confirmed by bunch of official examples) isn't about doing custom hooks.
Anyways, I managed to solve the issue, turns out it was issue with clearing fetch mocks between the tests.
Official documentation focuses only on custom hooks, which in my opinion is big overcomplication - core usage (confirmed by bunch of official examples) isn't about doing custom hooks.
Anyways, I managed to solve the issue, turns out it was issue with clearing fetch mocks between the tests.
@adriandmitroca - Could you elaborate on this? I'm still unable successfully clear the queryCache between tests.
@josh-biddick Here's my setupTests.js:
import "@testing-library/jest-dom"
import { queryCache } from "react-query"
import fetch from "jest-fetch-mock"
require("jest-fetch-mock").enableMocks()
beforeEach(() => {
jest.clearAllMocks()
fetch.resetMocks()
queryCache.clear()
})
Most helpful comment
Hi,
(I write this for the record and maybe to help other people)
Here an example of what i did to test a custom hook wrapping
useQuery: