我有一个自定义挂钩,可以返回 api 调用。它看起来像这样:
export const useApiData = () => {
const queryClient = useQueryClient();
const [networkError, setNetworkError] = useState<string>(null);
const getCase = (caseId: number) =>
useQuery({
queryKey: `case-${caseId}`,
queryFn: (): Promise<AxiosResponse<CaseDto>> => CASE_API.api.getCase(caseId),
staleTime: Infinity,
suspense: true,
});
const api = {
getCase,
};
return { api, networkError };
};
我想用 sinon 存根来存根这个自定义钩子:
export function mockGetCase(sinonSandbox: SinonSandbox = sinon.createSandbox()) {
const { api } = useApiData();
sinonSandbox.stub(api, "getCase").callsFake(() => {
return () => Promise.resolve(caseMockApiData);
});
return sinonSandbox;
}
但是,这行不通,因为我在 React 组件之外调用 hook:
警告:挂机无效。钩子只能在 功能组件的主体。这可能发生在其中一个 以下原因:
- 你可能有不匹配的 React 版本和渲染器(例如 React DOM)
- 你可能违反了 Hooks 规则
- 您可能在同一个应用程序中有多个 React 副本有关如何调试的提示,请参阅https://reactjs.org/link/invalid-hook-call
如何使用 sinon 存根自定义 React Hook?
并解决这个问题。
我在我的组件中使用这样的钩子:
const { caseId } = useCase();
const { api } = useApiData();
const {
data: { data: case },
} = api.getCase(caseId);
CASE_API 看起来像这样:
export const CASE_API: CaseApi<CaseDto> = useApi(
new CaseApi({ baseURL: environment.url.case }),
"case",
"gcp"
);
钩子
useApi
看起来像这样:
export function useApi<T extends AxiosClient>(api: T, app: string, cluster: string): T {
api.instance.interceptors.request.use(
async (config: InternalAxiosRequestConfig) => {
const secHeaders = await createDefaultHeaders(app, cluster);
Object.assign(config.headers, secHeaders);
return config;
},
function (error) {
// error handling here
return Promise.reject(error);
}
);
api.instance.interceptors.response.use(
function (response) {
return response;
},
function (error) {
return Promise.reject(error);
}
);
return api;
}
const createDefaultHeaders = async (app: string, cluster: string) => {
const idToken = await SecuritySessionUtils.getSecurityTokenForApp(app, cluster);
const correlationId = SecuritySessionUtils.getCorrelationId();
return {
Authorization: "Bearer " + idToken,
"Content-type": "application/json; charset=UTF-8",
"X-Correlation-ID": correlationId,
"Call-Id": correlationId,
"Consumer-Id": SecuritySessionUtils.getAppName(),
};
};
我尝试使用 mock service worker 来模拟 api 调用:
const server = setupServer(
rest.get(`${environment.url.case}/api/case/:caseId`, (req, res, ctx) => {
return res(ctx.set("Content-Type", "application/json"), ctx.body(JSON.stringify(caseMockApiData)));
})
);
before(() => {
server.listen();
});
afterEach(() => server.resetHandlers());
after(() => server.close());
但是,然后我得到一个错误:
TypeError: 只支持绝对 URL 在 Function.getSecurityTokenForApp
getSecurityTokenForApp
是从 useApi 调用的函数:
const createDefaultHeaders = async (app: string, cluster: string) => {
const idToken = await SecuritySessionUtils.getSecurityTokenForApp(app, cluster);
它看起来像这样:
static async getSecurityTokenForApp(app: string, cluster?: string) {
const tokenReq = await fetch("/token", {
method: "POST",
body: JSON.stringify({ app, cluster }),
headers: { "Content-type": "application/json; charset=UTF-8" },
});
return await tokenReq.text();
}
你应该这样使用钩子:
const { caseId } = useCase();
const { isLoading, isError, data, ...otherStuffFromReactQuery } = useApiData(caseId);
这里是钩子:
export const useApiData = (caseId: number) => {
return useQuery({
queryKey: `case-${caseId}`,
queryFn: (): Promise<AxiosResponse<CaseDto>> => CASE_API.api.getCase(caseId),
staleTime: Infinity,
suspense: true,
})
}