React-Query - 使用 react-testing-library 进行单元测试
React-Query - Unit Test with react-testing-library
我有一个 Dashboard
组件,其中包含一个子组件,例如Child
使用 react-query。
我有一个针对 Dashboard
组件的现有单元测试开始失败,错误是:
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);
测试片段:
const queryClient = new QueryClient();
const { getByTestId, getByRole } = render(
<IntlProvider locale="en" messages={messages}>
<QueryClientProvider client={queryClient}>
<Dashboard />
</QueryClientProvider>
</IntlProvider>,
);
我阅读了有关测试的文档:
https://react-query.tanstack.com/guides/testing#our-first-test
但我不想一定要使用 renderHook
,因为我对结果不感兴趣。
编辑:
Child
组件正在使用函数:
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,
);
}
这样称呼:
const { data, error, isFetching, isError } = usePosts({
rqKey,
url,
extraConfig,
});
根据你的回答,我应该创建一个单独的函数:
async () => {
const result = await window.fetch(url, options);
const data = await result.json();
return data;
},
例如
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,
);
}
然后在测试中模拟它。
如果我这样做,我将如何访问:error, isFetching, isError
因为 usePosts()
现在 return 一个 Promise<QueryObserverResult<unknown, unknown>>
编辑 2:
我尝试简化我的代码:
export async function useFetch({ queryKey }: any) {
const [_key, { url, options }] = queryKey;
const res = await window.fetch(url, options);
return await res.json();
}
然后用作:
const { isLoading, error, data, isError } = useQuery(
[rqKey, { url, options }],
useFetch,
extraConfig,
);
所有作品。
在 Dashboard
测试中,我执行以下操作:
import * as useFetch from ".";
和
jest.spyOn(useFetch, "useFetch").mockResolvedValue(["asdf", "asdf"]);
和
render(
<IntlProvider locale="en" messages={messages}>
<QueryClientProvider client={queryClient}>
<Dashboard />
</QueryClientProvider>
</IntlProvider>,
);
然后 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,
您提到的文档页面解释了如何测试依赖于 React Query 的自定义挂钩。您是在使用基于 React Query 的自定义挂钩,还是只想测试使用 useQuery(React Query 提供的挂钩)的组件?
如果您只想测试使用 useQuery 的 Child,您应该模拟您的“请求函数”(return 承诺的函数,用作 useQuery 的第二个参数),并在没有任何提供程序的情况下呈现您的测试组件。
例如,在 Child 中说你有
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
...并且 getFoos 在 path/to/file/defining/getFoos.ts
中定义为
const getFoos = async (): Promise<string[]> => await fetch(...);
...然后在 Child.test.tsx 你可以做
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 />
答案:
虽然上面的答案帮助我朝着正确的方向前进,但潜在的问题是我使用 mockImplementation 来提供上下文,然后使 QueryClientProvider 给出的上下文变得无用,例如
jest.spyOn(React, "useContext").mockImplementation(() => ({
...
}));
我最终删除了 mockImplementation 并在我的 UserStateContext.Provider 中添加了 QueryClientProvider 并解决了问题:
render(
<IntlProvider locale="en" messages={messages}>
<UserStateContext.Provider value={value}>
<QueryClientProvider client={queryClient}>
<Dashboard />
</QueryClientProvider>
</UserStateContext.Provider>
</IntlProvider>,
);
我有一个 Dashboard
组件,其中包含一个子组件,例如Child
使用 react-query。
我有一个针对 Dashboard
组件的现有单元测试开始失败,错误是:
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);
测试片段:
const queryClient = new QueryClient();
const { getByTestId, getByRole } = render(
<IntlProvider locale="en" messages={messages}>
<QueryClientProvider client={queryClient}>
<Dashboard />
</QueryClientProvider>
</IntlProvider>,
);
我阅读了有关测试的文档:
https://react-query.tanstack.com/guides/testing#our-first-test
但我不想一定要使用 renderHook
,因为我对结果不感兴趣。
编辑:
Child
组件正在使用函数:
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,
);
}
这样称呼:
const { data, error, isFetching, isError } = usePosts({
rqKey,
url,
extraConfig,
});
根据你的回答,我应该创建一个单独的函数:
async () => {
const result = await window.fetch(url, options);
const data = await result.json();
return data;
},
例如
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,
);
}
然后在测试中模拟它。
如果我这样做,我将如何访问:error, isFetching, isError
因为 usePosts()
现在 return 一个 Promise<QueryObserverResult<unknown, unknown>>
编辑 2:
我尝试简化我的代码:
export async function useFetch({ queryKey }: any) {
const [_key, { url, options }] = queryKey;
const res = await window.fetch(url, options);
return await res.json();
}
然后用作:
const { isLoading, error, data, isError } = useQuery(
[rqKey, { url, options }],
useFetch,
extraConfig,
);
所有作品。
在 Dashboard
测试中,我执行以下操作:
import * as useFetch from ".";
和
jest.spyOn(useFetch, "useFetch").mockResolvedValue(["asdf", "asdf"]);
和
render(
<IntlProvider locale="en" messages={messages}>
<QueryClientProvider client={queryClient}>
<Dashboard />
</QueryClientProvider>
</IntlProvider>,
);
然后 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,
您提到的文档页面解释了如何测试依赖于 React Query 的自定义挂钩。您是在使用基于 React Query 的自定义挂钩,还是只想测试使用 useQuery(React Query 提供的挂钩)的组件?
如果您只想测试使用 useQuery 的 Child,您应该模拟您的“请求函数”(return 承诺的函数,用作 useQuery 的第二个参数),并在没有任何提供程序的情况下呈现您的测试组件。
例如,在 Child 中说你有
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
...并且 getFoos 在 path/to/file/defining/getFoos.ts
中定义为
const getFoos = async (): Promise<string[]> => await fetch(...);
...然后在 Child.test.tsx 你可以做
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 />
答案:
虽然上面的答案帮助我朝着正确的方向前进,但潜在的问题是我使用 mockImplementation 来提供上下文,然后使 QueryClientProvider 给出的上下文变得无用,例如
jest.spyOn(React, "useContext").mockImplementation(() => ({
...
}));
我最终删除了 mockImplementation 并在我的 UserStateContext.Provider 中添加了 QueryClientProvider 并解决了问题:
render(
<IntlProvider locale="en" messages={messages}>
<UserStateContext.Provider value={value}>
<QueryClientProvider client={queryClient}>
<Dashboard />
</QueryClientProvider>
</UserStateContext.Provider>
</IntlProvider>,
);