在 React 测试库中运行异步测试时,Apollo useQuery() 抛出错误:ECONNREFUSED

Nei*_*rdi 12 javascript reactjs create-react-app apollo-client react-testing-library

更新: \n尝试升级到 React 18 和 RTL 13,但这并没有解决问题。

\n
    \n
  • 反应16.4.0
  • \n
  • 反应脚本 5.0.1
  • \n
  • 阿波罗客户端3.5.9
  • \n
  • React 测试库 12.1.2
  • \n
\n

useQuery()我\xe2\x80\x99m 在测试挂载时调用 Apollo 的 React 组件时遇到了长期问题。如果我使用异步 RTL 方法,GraphQL 查询将失败并出现ECONNREFUSED错误。如果我使用同步 RTL 方法,我不会收到此错误。但是,同步方法不起作用,因为断言在loadingequals时运行true时运行。因此,数据尚未从查询中返回,因此没有可呈现的内容。

\n

以下是引发此错误的测试中的代码片段:

\n
it(\'renders\', async () => {\n  renderGrid()\n  const columnHeading = await screen.findByText(/Primary Household Member/i)\n  expect(columnHeading).toBeInTheDocument()\n})\n
Run Code Online (Sandbox Code Playgroud)\n

我已附上控制台中生成的内容的屏幕截图(见下文)。\n如果我将代码更改为同步,如下所示:

\n
it(\'renders\', () => {\n  renderGrid()\n  const columnHeading = screen.getByText(/Primary Household Member/i)\n  expect(columnHeading).toBeInTheDocument()\n})\n
Run Code Online (Sandbox Code Playgroud)\n

我没有得到ECONNREFUSED错误。然而,在这种情况下,loadingistruedata以及error是未定义的。我无法测试网格是否呈现了表格,因为在这种情况下,组件只是返回<div>loading...</div>. 我尝试将渲染函数包装起来,act()但这不会改变任何东西。我也尝试waitFor()过没有效果。

\n

我还尝试了同步代码并将getBy()调用和断言包装在setTimeout(). 有趣的是,这实际上会产生误报。网格不会渲染,我可以断言任何我想要的东西并且它会通过。(不确定这是 Jest bug 还是 RTL bug),

\n

我将包含测试的完整代码。请注意,我正在将一些数据写入客户端缓存。这是因为除了发出网络请求之外,组件还通过使用指令查询属性来获取一些本地状态@client

\n

以下是查询:

\n
import gql from \'graphql-tag\'\n\n// this data is in the client-side cache only\n// it is local state\n// we write this to the cache in the test\nexport const GET_REPORTING_GRID_SETTINGS = gql`\n    query GetReportingGridSettings {\n        reportingGridQueryVariables @client\n    }\n`\n// this data is in the client-side cache only\n// it is local state\n// we write this to the cache in the test\nexport const GET_COLUMN_SELECTIONS = gql`\n    query ColumnSelections {\n        columnSelections @client {\n            household\n            individual\n            cases\n            activities\n        }\n    }\n`\n// this data is in the client-side cache only\n// it is local state\n// we write this to the cache in the test\nexport const GET_REPORTING_MODAL_STATE = gql`\n    query GetReportingModalState {\n        showReportingMainModal @client\n    }\n`\n\n// this is a network query\n// we pass this in apollo mocks\nexport const ME = gql`\n    query me {\n        me {\n            id\n            isACaseManager\n            fullName\n            role\n            userable {\n                ... on CaseManager {\n                    id\n                    organization {\n                        id\n                        name\n                        slug\n                    }\n                    locations {\n                        id\n                        name\n                        slug\n                        customFields {\n                            id\n                            label\n                        }\n                    }\n                }\n            }\n            email\n        }\n    }\n`\n\n// this is a network query\n// we pass this in apollo mocks\nexport const GET_INDIVIDUAL_DEMOGRAPHICS = gql`\n    query getDemographics(\n        $pageSize: Int\n        $pageNumber: Int\n        $sort: [IndividualDemographicReportSortInput!]\n        $filter: IndividualDemographicReportFilterInput\n        $searchTerm: String\n    ) {\n        individualDemographicReport(\n            pageSize: $pageSize\n            pageNumber: $pageNumber\n            sort: $sort\n            filter: $filter\n            searchTerm: $searchTerm\n        ) {\n            totalCount\n            pageCount\n            nodes {\n                id\n                fullName\n                annualIncome\n                age\n                dateOfBirth\n                displayDateOfBirth @client #type policy\n                relationshipToClient\n                employmentCount\n                isCurrentlyWorking\n                displayIsCurrentlyWorking @client #type policy\n                employmentStatus\n                additionalIncome\n                displayAdditionalIncome @client #type policy\n                alimonyAmount\n                primaryAccountHolder {\n                    fullName\n                    clientLocations {\n                        id\n                    }\n                    lastYearAdjustedGrossIncome\n                    taxFilingStatus\n                    displayLastYearAdjustedGrossIncome @client #type policy\n                    displayTaxFilingStatus @client #type policy\n                }\n                demographic {\n                    id\n                    gender\n                    race\n                    ethnicity\n                    education\n                    healthInsurance\n                    hasHealthInsurance\n                    displayHasHealthInsurance @client #type policy\n                    isStudent\n                    displayIsStudent @client #type policy\n                    isVeteran\n                    displayIsVeteran @client #type policy\n                    isDisabled\n                    isPregnant\n                    displayIsPregnant @client #type policy\n                    isUsCitizen\n                    displayIsUsCitizen @client #type policy\n                    immigrationStatus\n                    lengthOfPermanentResidency\n                    courseLoad\n                    hasWorkStudy\n                    displayHasWorkStudy @client #type policy\n                    expectedFamilyContribution\n                    costOfAttendance\n                    displayEfc @client #type policy\n                    displayCoa @client #type policy\n                    courseLoad\n                }\n                alimonyAmount\n                childSupportAmount\n                pensionAmount\n                ssdSsiAmount\n                unemploymentInsuranceAmount\n                vaBenefitsAmount\n                workersCompensationAmount\n                otherAdditionalIncomeAmount\n                savingsAmount\n                claimedAsDependent\n                displayClaimedAsDependent @client #type policy\n            }\n        }\n    }\n`\n
Run Code Online (Sandbox Code Playgroud)\n

这是测试文件的完整内容。接下来是控制台屏幕截图:

\n
import React from \'react\'\nimport { render, screen } from \'Utils/test-utils\'\nimport {\n  GET_INDIVIDUAL_DEMOGRAPHICS,\n  GET_REPORTING_GRID_SETTINGS,\n  GET_COLUMN_SELECTIONS,\n  GET_REPORTING_MODAL_STATE,\n} from \'Components/Reporting/Hooks/gql\'\nimport mockCache, {\n  reportingIndividualDateRangeStartVar,\n  reportingIndividualDateRangeEndVar,\n} from \'ApolloClient/caseManagementCache\'\nimport {\n  apolloMocks,\n  mockReportingGridState,\n  mockReportingColumnsData,\n  mockReportingModalsData,\n} from \'./fixtures\'\nimport ReportingGrid from \'./ReportingGrid\'\nimport getColumnsData from \'Components/CaseManagement/Reporting/Grids/Demographics/Individual/columnsData\'\n\n// Set dateRanges in the cache to match the values used in the Apollo mocks\nbeforeEach(() => {\n  // Set dateRanges in the cache to match the values used in the Apollo mocks\n  reportingIndividualDateRangeStartVar(\'2022-01-01T05:00:00.000Z\')\n  reportingIndividualDateRangeEndVar(\'2022-06-03T13:56:36.662Z\')\n\n  mockCache.writeQuery(\n    {\n      query: GET_REPORTING_GRID_SETTINGS,\n      data: mockReportingGridState,\n    },\n    {\n      query: GET_COLUMN_SELECTIONS,\n      data: mockReportingColumnsData,\n    },\n    {\n      query: GET_REPORTING_MODAL_STATE,\n      data: mockReportingModalsData,\n    }\n  )\n})\n\nconst renderGrid = () => {\n  render(\n    <ReportingGrid\n      dataQueryTag={GET_INDIVIDUAL_DEMOGRAPHICS}\n      defaultSortField={\'fullName\'}\n      getColumnsData={getColumnsData}\n      sortable\n      pageable\n      reportEnum={\'INDIVIDUAL\'}\n    />,\n    {\n      apolloMocks,\n      cache: mockCache,\n      addTypename: true,\n    }\n  )\n}\n\nit(\'renders\', () => {\n  renderGrid()\n  const columnHeading = screen.getByText(/Primary Household Member/i)\n  expect(columnHeading).toBeInTheDocument()\n})\n
Run Code Online (Sandbox Code Playgroud)\n

在此输入图像描述

\n

这是 test-utils.js 文件:

\n
import React from \'react\'\nimport { Provider as ReduxProvider } from \'react-redux\'\nimport configureStore from \'redux-mock-store\'\nimport { MockedProvider as MockedApolloProvider } from \'@apollo/client/testing\'\nimport { render } from \'@testing-library/react\'\nimport { renderHook } from \'@testing-library/react-hooks\'\nimport { BrowserRouter as Router } from \'react-router-dom\'\nimport store from \'../Store\'\nimport thunk from \'redux-thunk\'\n\nimport { ThemeProvider as StyledThemeProvider } from \'styled-components/macro\'\nimport { ThemeProvider as MuiThemeProvider } from \'@material-ui/core/styles\'\nimport styledTheme from \'Shared/Theme\'\nimport { mainMuiTheme } from \'Shared/Theme/muiTheme\'\n\n/**\n * [dispatch Dispatch from our store\n * @type {Function}\n */\nconst { dispatch } = store\n\n/**\n * Generates a mock store given an initial state. Middlewares are optional and\n * defaults to thunk. The mock store is used to set initial application state to\n * supply data to UI components. The mock store can also be used to test for\n * dispatched actions.\n *\n * Reducers can be unit tested separately. Testing the full range of user\n * interaction, to action dispatch, to reducer input/output, to state change,\n * would be done in an integration test. The mock store does not invoke reducers\n * or change state.\n *\n * {@link https://github.com/reduxjs/redux-mock-store|redux-mock-store}\n *\n * @param  {Object} initialState Initial redux state\n * @param  {Array}  middlewares  Optional middleware, default: [thunk]\n * @return {Object}              Mock Redux Store\n */\nconst createMockStore = (initialState, middlewares = [thunk]) =>\n  configureStore(middlewares)(initialState)\n\nconst WrapperWithProviders =\n  ({ reduxStore, apolloProps }) =>\n  ({ children }) =>\n    (\n      <ReduxProvider store={reduxStore}>\n        <MockedApolloProvider {...apolloProps}>\n          <StyledThemeProvider theme={styledTheme.mode[\'light\']}>\n            <MuiThemeProvider theme={mainMuiTheme}>\n              <Router>{children}</Router>\n            </MuiThemeProvider>\n          </StyledThemeProvider>\n        </MockedApolloProvider>\n      </ReduxProvider>\n    )\n\n// Did we receive a (mocked) reduxStore property in the optional second argument?\n// If so, pass it in to WrapperWithProviders\n// If not, pass the default redux store imported at the top of this file\nconst getReduxStore = (options) =>\n  options && options.reduxStore ? options.reduxStore : store\n\n// Did we receive an optional second argument?\n// Did it contain a reduxStore property?\n// If so, remove it from the options before passing them to RTL render()\n// (That argument is for WrapperWithProviders, not RTL)\nconst getOptions = (options) => {\n  if (!options) return\n  const {\n    reduxStore,\n    apolloMocks,\n    addTypename,\n    cache,\n    resolvers,\n    ...remainingOptions\n  } = options\n  return remainingOptions\n}\n\nconst mockWrapper = (options) => {\n  let apolloMocks = []\n  let addTypename = false\n  let cache = null\n  let resolvers = null\n\n  if (options) {\n    apolloMocks = options.apolloMocks || apolloMocks\n    addTypename = options.addTypename || addTypename\n    cache = options.cache || cache\n    resolvers = options.resolvers || resolvers\n  }\n\n  const apolloProps = {\n    mocks: apolloMocks,\n    addTypename,\n    resolvers,\n  }\n\n  if (cache) {\n    apolloProps.cache = cache\n  }\n  const reduxStore = getReduxStore(options)\n  return {\n    wrapper: WrapperWithProviders({\n      reduxStore,\n      apolloProps,\n    }),\n    ...getOptions(options),\n  }\n}\n\nconst customRender = (ui, options) => {\n  return render(ui, mockWrapper(options))\n}\n\nconst customRenderHook = (cb, options) => {\n  return renderHook(cb, mockWrapper(options))\n}\n// re-export everything\nexport * from \'@testing-library/react\'\n\n// override render method\nexport {\n  customRender as render,\n  createMockStore,\n  customRenderHook as renderHook,\n  dispatch,\n}\n
Run Code Online (Sandbox Code Playgroud)\n

小智 0

恐怕我对 Apollo 库不熟悉,但在我看来,模拟不起作用;相反,它正在尝试对 localhost:80 进行真正的 http 调用。

我希望当您不“等待”时看不到错误的原因是因为http请求本身是异步的,因此如果没有“等待”,则在执行断言之前它没有机会运行考试。

查看代码,我猜测您正在用数据预填充缓存,因此它不应该尝试网络访问?我建议使用调试器单步执行“mount”函数或挂钩,并尝试查看模拟数据是否最终达到您期望的位置。我经常对 java 脚本值没有达到我预期的结果感到困惑!

抱歉我无法提供更多帮助。