【问题标题】:Apollo useQuery() throws Error: ECONNREFUSED when running async test in React Testing LibraryApollo useQuery() 在 React 测试库中运行异步测试时抛出错误:ECONNREFUSED
【发布时间】:2022-06-10 18:42:06
【问题描述】:

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

  • 反应 16.4.0
  • 反应脚本 5.0.1
  • Apollo 客户端 3.5.9
  • React 测试库 12.1.2

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

以下是引发此错误的测试中的代码 sn-p:

it('renders', async () => {
  renderGrid()
  const columnHeading = await screen.findByText(/Primary Household Member/i)
  expect(columnHeading).toBeInTheDocument()
})

我附上了控制台中生成的屏幕截图(见下文)。 如果我像这样将代码更改为同步:

it('renders', () => {
  renderGrid()
  const columnHeading = screen.getByText(/Primary Household Member/i)
  expect(columnHeading).toBeInTheDocument()
})

我没有收到 ECONNREFUSED 错误。但是,在这种情况下,loadingtruedataerror 是未定义的。我无法测试网格是否呈现了表格,因为在这种情况下,组件只返回<div>loading...</div>。我尝试将渲染函数包装在act() 中,但这并没有改变任何东西。我也尝试过waitFor() 无济于事。

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

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

这里是查询:

import gql from 'graphql-tag'

// this data is in the client-side cache only
// it is local state
// we write this to the cache in the test
export const GET_REPORTING_GRID_SETTINGS = gql`
    query GetReportingGridSettings {
        reportingGridQueryVariables @client
    }
`
// this data is in the client-side cache only
// it is local state
// we write this to the cache in the test
export const GET_COLUMN_SELECTIONS = gql`
    query ColumnSelections {
        columnSelections @client {
            household
            individual
            cases
            activities
        }
    }
`
// this data is in the client-side cache only
// it is local state
// we write this to the cache in the test
export const GET_REPORTING_MODAL_STATE = gql`
    query GetReportingModalState {
        showReportingMainModal @client
    }
`

// this is a network query
// we pass this in apollo mocks
export const ME = gql`
    query me {
        me {
            id
            isACaseManager
            fullName
            role
            userable {
                ... on CaseManager {
                    id
                    organization {
                        id
                        name
                        slug
                    }
                    locations {
                        id
                        name
                        slug
                        customFields {
                            id
                            label
                        }
                    }
                }
            }
            email
        }
    }
`

// this is a network query
// we pass this in apollo mocks
export const GET_INDIVIDUAL_DEMOGRAPHICS = gql`
    query getDemographics(
        $pageSize: Int
        $pageNumber: Int
        $sort: [IndividualDemographicReportSortInput!]
        $filter: IndividualDemographicReportFilterInput
        $searchTerm: String
    ) {
        individualDemographicReport(
            pageSize: $pageSize
            pageNumber: $pageNumber
            sort: $sort
            filter: $filter
            searchTerm: $searchTerm
        ) {
            totalCount
            pageCount
            nodes {
                id
                fullName
                annualIncome
                age
                dateOfBirth
                displayDateOfBirth @client #type policy
                relationshipToClient
                employmentCount
                isCurrentlyWorking
                displayIsCurrentlyWorking @client #type policy
                employmentStatus
                additionalIncome
                displayAdditionalIncome @client #type policy
                alimonyAmount
                primaryAccountHolder {
                    fullName
                    clientLocations {
                        id
                    }
                    lastYearAdjustedGrossIncome
                    taxFilingStatus
                    displayLastYearAdjustedGrossIncome @client #type policy
                    displayTaxFilingStatus @client #type policy
                }
                demographic {
                    id
                    gender
                    race
                    ethnicity
                    education
                    healthInsurance
                    hasHealthInsurance
                    displayHasHealthInsurance @client #type policy
                    isStudent
                    displayIsStudent @client #type policy
                    isVeteran
                    displayIsVeteran @client #type policy
                    isDisabled
                    isPregnant
                    displayIsPregnant @client #type policy
                    isUsCitizen
                    displayIsUsCitizen @client #type policy
                    immigrationStatus
                    lengthOfPermanentResidency
                    courseLoad
                    hasWorkStudy
                    displayHasWorkStudy @client #type policy
                    expectedFamilyContribution
                    costOfAttendance
                    displayEfc @client #type policy
                    displayCoa @client #type policy
                    courseLoad
                }
                alimonyAmount
                childSupportAmount
                pensionAmount
                ssdSsiAmount
                unemploymentInsuranceAmount
                vaBenefitsAmount
                workersCompensationAmount
                otherAdditionalIncomeAmount
                savingsAmount
                claimedAsDependent
                displayClaimedAsDependent @client #type policy
            }
        }
    }
`

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

import React from 'react'
import { render, screen } from 'Utils/test-utils'
import {
  GET_INDIVIDUAL_DEMOGRAPHICS,
  GET_REPORTING_GRID_SETTINGS,
  GET_COLUMN_SELECTIONS,
  GET_REPORTING_MODAL_STATE,
} from 'Components/Reporting/Hooks/gql'
import mockCache, {
  reportingIndividualDateRangeStartVar,
  reportingIndividualDateRangeEndVar,
} from 'ApolloClient/caseManagementCache'
import {
  apolloMocks,
  mockReportingGridState,
  mockReportingColumnsData,
  mockReportingModalsData,
} from './fixtures'
import ReportingGrid from './ReportingGrid'
import getColumnsData from 'Components/CaseManagement/Reporting/Grids/Demographics/Individual/columnsData'

// Set dateRanges in the cache to match the values used in the Apollo mocks
beforeEach(() => {
  // Set dateRanges in the cache to match the values used in the Apollo mocks
  reportingIndividualDateRangeStartVar('2022-01-01T05:00:00.000Z')
  reportingIndividualDateRangeEndVar('2022-06-03T13:56:36.662Z')

  mockCache.writeQuery(
    {
      query: GET_REPORTING_GRID_SETTINGS,
      data: mockReportingGridState,
    },
    {
      query: GET_COLUMN_SELECTIONS,
      data: mockReportingColumnsData,
    },
    {
      query: GET_REPORTING_MODAL_STATE,
      data: mockReportingModalsData,
    }
  )
})

const renderGrid = () => {
  render(
    <ReportingGrid
      dataQueryTag={GET_INDIVIDUAL_DEMOGRAPHICS}
      defaultSortField={'fullName'}
      getColumnsData={getColumnsData}
      sortable
      pageable
      reportEnum={'INDIVIDUAL'}
    />,
    {
      apolloMocks,
      cache: mockCache,
      addTypename: true,
    }
  )
}

it('renders', () => {
  renderGrid()
  const columnHeading = screen.getByText(/Primary Household Member/i)
  expect(columnHeading).toBeInTheDocument()
})

这里是 test-utils.js 文件:

import React from 'react'
import { Provider as ReduxProvider } from 'react-redux'
import configureStore from 'redux-mock-store'
import { MockedProvider as MockedApolloProvider } from '@apollo/client/testing'
import { render } from '@testing-library/react'
import { renderHook } from '@testing-library/react-hooks'
import { BrowserRouter as Router } from 'react-router-dom'
import store from '../Store'
import thunk from 'redux-thunk'

import { ThemeProvider as StyledThemeProvider } from 'styled-components/macro'
import { ThemeProvider as MuiThemeProvider } from '@material-ui/core/styles'
import styledTheme from 'Shared/Theme'
import { mainMuiTheme } from 'Shared/Theme/muiTheme'

/**
 * [dispatch Dispatch from our store
 * @type {Function}
 */
const { dispatch } = store

/**
 * Generates a mock store given an initial state. Middlewares are optional and
 * defaults to thunk. The mock store is used to set initial application state to
 * supply data to UI components. The mock store can also be used to test for
 * dispatched actions.
 *
 * Reducers can be unit tested separately. Testing the full range of user
 * interaction, to action dispatch, to reducer input/output, to state change,
 * would be done in an integration test. The mock store does not invoke reducers
 * or change state.
 *
 * {@link https://github.com/reduxjs/redux-mock-store|redux-mock-store}
 *
 * @param  {Object} initialState Initial redux state
 * @param  {Array}  middlewares  Optional middleware, default: [thunk]
 * @return {Object}              Mock Redux Store
 */
const createMockStore = (initialState, middlewares = [thunk]) =>
  configureStore(middlewares)(initialState)

const WrapperWithProviders =
  ({ reduxStore, apolloProps }) =>
  ({ children }) =>
    (
      <ReduxProvider store={reduxStore}>
        <MockedApolloProvider {...apolloProps}>
          <StyledThemeProvider theme={styledTheme.mode['light']}>
            <MuiThemeProvider theme={mainMuiTheme}>
              <Router>{children}</Router>
            </MuiThemeProvider>
          </StyledThemeProvider>
        </MockedApolloProvider>
      </ReduxProvider>
    )

// Did we receive a (mocked) reduxStore property in the optional second argument?
// If so, pass it in to WrapperWithProviders
// If not, pass the default redux store imported at the top of this file
const getReduxStore = (options) =>
  options && options.reduxStore ? options.reduxStore : store

// Did we receive an optional second argument?
// Did it contain a reduxStore property?
// If so, remove it from the options before passing them to RTL render()
// (That argument is for WrapperWithProviders, not RTL)
const getOptions = (options) => {
  if (!options) return
  const {
    reduxStore,
    apolloMocks,
    addTypename,
    cache,
    resolvers,
    ...remainingOptions
  } = options
  return remainingOptions
}

const mockWrapper = (options) => {
  let apolloMocks = []
  let addTypename = false
  let cache = null
  let resolvers = null

  if (options) {
    apolloMocks = options.apolloMocks || apolloMocks
    addTypename = options.addTypename || addTypename
    cache = options.cache || cache
    resolvers = options.resolvers || resolvers
  }

  const apolloProps = {
    mocks: apolloMocks,
    addTypename,
    resolvers,
  }

  if (cache) {
    apolloProps.cache = cache
  }
  const reduxStore = getReduxStore(options)
  return {
    wrapper: WrapperWithProviders({
      reduxStore,
      apolloProps,
    }),
    ...getOptions(options),
  }
}

const customRender = (ui, options) => {
  return render(ui, mockWrapper(options))
}

const customRenderHook = (cb, options) => {
  return renderHook(cb, mockWrapper(options))
}
// re-export everything
export * from '@testing-library/react'

// override render method
export {
  customRender as render,
  createMockStore,
  customRenderHook as renderHook,
  dispatch,
}

【问题讨论】:

    标签: javascript reactjs create-react-app apollo-client react-testing-library


    【解决方案1】:

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

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

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

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

    【讨论】:

    • 感谢您的反馈。写入缓存的查询是仅存在于客户端缓存中的数据,并使用 @client 指令进行查询(这有点像 Apollo 对 Redux 的回答。您可以使用它来编写和查询应用程序状态)。其他两个查询是网络查询,因此它们在 Apollo 模拟中。 ME 查询模拟似乎有效。如果我从模拟中删除它,阿波罗会抱怨它丢失了。爆炸的是 GET_INDIVIDUAL_DEMOGRAPHICS。我以为我今天早上有一个尤里卡时刻。此查询使用类型策略。我删除了这些字段,但遗憾的是,同样的交易。
    猜你喜欢
    • 1970-01-01
    • 2020-02-26
    • 1970-01-01
    • 2012-09-29
    • 1970-01-01
    • 2020-03-18
    • 2018-11-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多