【问题标题】:Testing components using React Relay使用 React Relay 测试组件
【发布时间】:2021-06-29 20:45:18
【问题描述】:

我正在使用新的 Relay Hooks 并发现很难通过测试。我遇到了他们docs 中提到的问题。

如果在usePreloadedQuery之前和之后添加console.log,只会命中“before”调用

//sample test

jest.useFakeTimers()

test("a list of entries is displayed when the component mounts", async () => {
  const environment = createMockEnvironment()

  environment.mock.queueOperationResolver(operation => {
    return MockPayloadGenerator.generate(operation, {
      Entry() {
        return {
          id: "123",
          title: "hello",
          urlKey: "abc"
        }
      }
    })
  })

  relay.mock.queuePendingOperation(EntryListQuery, {})

  render(<RelayEnvironmentProvider environment={environment}>
           <Entries />
         </RelayEnvironmentProvider>
  )

  jest.runAllImmediates()

  expect(await screen.getByText(/hello/i)).toBeInTheDocument()
})
//core component I am wanting to test
import { Suspense, useEffect } from "react"
import { useQueryLoader } from "react-relay/hooks"
import { Loading } from "./Loading"
import { EntryList, EntryListQuery } from "./EntryList"


const Entries = () => {
  const [queryReference, loadQuery, disposeQuery] = useQueryLoader(EntryListQuery)

  useEffect(() => {
    if (!queryReference) loadQuery()
  }, [disposeQuery, loadQuery, queryReference])

  if (!queryReference) return <Loading />

  return (
    <Suspense fallback={<Loading />}>
      <EntryList queryReference={queryReference} />
    </Suspense>
  )
}

export { Entries }
//the core component's child component
import { usePreloadedQuery } from "react-relay/hooks"
import graphql from "babel-plugin-relay/macro"
import { Link } from "react-router-dom"
import { Entry } from "./Entry"

const EntryListQuery = graphql`
  query EntryListQuery {
    queryEntry {
      id
      title
      urlKey
    }
  }
`

const EntryList = ({ queryReference }) => {
  const { queryEntry } = usePreloadedQuery(EntryListQuery, queryReference)

  return (
    <section>
      <div className="flex justify-between items-center">
        <p>search</p>
        <Link to="?action=new">New Entry</Link>
      </div>
      <ul>
        {queryEntry.map(entry => {
          if (entry) return <Entry key={entry.id} entry={entry} />
          return null
        })}
      </ul>
    </section>
  )
}
export { EntryList, EntryListQuery }

我发现loadQuery 正在被调用,但是我在queueOperationResolver 中的console.log 的任何内容都没有出现。如果我在usePreloadedQuery 之前添加一个console.log,它会输出,但之后不会。因此,EntryList 似乎被挂起,查询永远无法解决。

我发现如果我将测试更改为以下也不会触发任何错误,看起来queueOperationResolver 永远不会被调用。

environment.mock.queueOperationResolver(() => new Error("Uh-oh"))

当我在 EntryList 之前的 usePreloadedQuery 代码之前 console.log queryReference 时,它会输出如下所示的对象。所以我知道查询被正确传递了。

{
      kind: 'PreloadedQuery',
      environment: RelayModernEnvironment {
        configName: 'RelayModernMockEnvironment',
        _treatMissingFieldsAsNull: false,
        __log: [Function: emptyFunction],
        requiredFieldLogger: [Function: defaultRequiredFieldLogger],
        _defaultRenderPolicy: 'partial',
        _operationLoader: undefined,
        _operationExecutions: Map(1) { '643ead0ae575426fdd62800c27d6fef3{}' => 'active' },
        _network: { execute: [Function: execute] },
        _getDataID: [Function: defaultGetDataID],
        _publishQueue: RelayPublishQueue {
          _hasStoreSnapshot: false,
          _handlerProvider: [Function: RelayDefaultHandlerProvider],
          _pendingBackupRebase: false,
          _pendingData: Set(0) {},
          _pendingOptimisticUpdates: Set(0) {},
          _store: [RelayModernStore],
          _appliedOptimisticUpdates: Set(0) {},
          _gcHold: null,
          _getDataID: [Function: defaultGetDataID]
        },
        _scheduler: null,
        _store: RelayModernStore {
          _gcStep: [Function (anonymous)],
          _currentWriteEpoch: 0,
          _gcHoldCounter: 0,
          _gcReleaseBufferSize: 10,
          _gcRun: null,
          _gcScheduler: [Function: resolveImmediate],
          _getDataID: [Function: defaultGetDataID],
          _globalInvalidationEpoch: null,
          _invalidationSubscriptions: Set(0) {},
          _invalidatedRecordIDs: Set(0) {},
          __log: null,
          _queryCacheExpirationTime: undefined,
          _operationLoader: null,
          _optimisticSource: null,
          _recordSource: [RelayMapRecordSourceMapImpl],
          _releaseBuffer: [],
          _roots: [Map],
          _shouldScheduleGC: false,
          _storeSubscriptions: [RelayStoreSubscriptions],
          _updatedRecordIDs: Set(0) {},
          _shouldProcessClientComponents: undefined,
          getSource: [Function],
          lookup: [Function],
          notify: [Function],
          publish: [Function],
          retain: [Function],
          subscribe: [Function]
        },
        options: undefined,
        _isServer: false,
        __setNet: [Function (anonymous)],
        DEBUG_inspect: [Function (anonymous)],
        _missingFieldHandlers: undefined,
        _operationTracker: RelayOperationTracker {
          _ownersToPendingOperationsIdentifier: Map(0) {},
          _pendingOperationsToOwnersIdentifier: Map(0) {},
          _ownersIdentifierToPromise: Map(0) {}
        },
        _reactFlightPayloadDeserializer: undefined,
        _reactFlightServerErrorHandler: undefined,
        _shouldProcessClientComponents: undefined,
        execute: [Function: mockConstructor] {
          _isMockFunction: true,
          getMockImplementation: [Function (anonymous)],
          mock: [Getter/Setter],
          mockClear: [Function (anonymous)],
          mockReset: [Function (anonymous)],
          mockRestore: [Function (anonymous)],
          mockReturnValueOnce: [Function (anonymous)],
          mockResolvedValueOnce: [Function (anonymous)],
          mockRejectedValueOnce: [Function (anonymous)],
          mockReturnValue: [Function (anonymous)],
          mockResolvedValue: [Function (anonymous)],
          mockRejectedValue: [Function (anonymous)],
          mockImplementationOnce: [Function (anonymous)],
          mockImplementation: [Function (anonymous)],
          mockReturnThis: [Function (anonymous)],
          mockName: [Function (anonymous)],
          getMockName: [Function (anonymous)]
        },
        executeWithSource: [Function: mockConstructor] {
          _isMockFunction: true,
          getMockImplementation: [Function (anonymous)],
          mock: [Getter/Setter],
          mockClear: [Function (anonymous)],
          mockReset: [Function (anonymous)],
          mockRestore: [Function (anonymous)],
          mockReturnValueOnce: [Function (anonymous)],
          mockResolvedValueOnce: [Function (anonymous)],
          mockRejectedValueOnce: [Function (anonymous)],
          mockReturnValue: [Function (anonymous)],
          mockResolvedValue: [Function (anonymous)],
          mockRejectedValue: [Function (anonymous)],
          mockImplementationOnce: [Function (anonymous)],
          mockImplementation: [Function (anonymous)],
          mockReturnThis: [Function (anonymous)],
          mockName: [Function (anonymous)],
          getMockName: [Function (anonymous)]
        },
        ...

更新

我发现以下测试有效,因此这意味着在尝试模拟使用 useQueryLoader 的组件中的查询时我做错了。

//sample test
test("a list of entries is displayed when the component mounts", async () => {
  const environment = createMockEnvironment()

  environment.mock.queueOperationResolver(operation => {
    return MockPayloadGenerator.generate(operation, {
      Entry() {
        return {
          id: "123",
          title: "hello",
          urlKey: "abc"
        }
      }
    })
  })

  relay.mock.queuePendingOperation(EntryListQuery, {})
  
  const queryReference = loadQuery(environment, EntryListQuery, {}, {})

  render(<RelayEnvironmentProvider environment={environment}>
           <EntryList queryReference={queryReference=} />
         </RelayEnvironmentProvider>
  )

  expect(await screen.getByText(/hello/i)).toBeInTheDocument()
})

【问题讨论】:

    标签: javascript reactjs react-testing-library relayjs relay


    【解决方案1】:

    感谢 OP 和官方文档,我得到了它的工作。我的最终代码如下所示:

    import React, { ReactNode, Suspense } from "react";
    import { act, render, RenderAPI } from "@testing-library/react-native";
    import {
      createMockEnvironment,
      MockPayloadGenerator,
      RelayMockEnvironment,
    } from "relay-test-utils";
    import { loadQuery, RelayEnvironmentProvider } from "react-relay";
    import Component from "../../src/components/Component";
    import compiledQuery, {
      ComponentQuery,
    } from "../../src/components/__generated__/Component.graphql";
    
    type RenderWithProps = {
      environment: RelayMockEnvironment;
    };
    
    const renderWith = ({ environment }: RenderWithProps): RenderAPI => {
      const wrapper = ({ children }: { children: ReactNode }) => {
        return (
          <RelayEnvironmentProvider environment={environment}>
            <Suspense fallback={<View></View>}>{children}</Suspense>
          </RelayEnvironmentProvider>
        );
      };
      const queryRef = loadQuery<ComponentQuery>(
        environment,
        compiledQuery,
        {
          id: "testId",
        }
      );
      return render(<Component queryRef={queryRef} />, { wrapper });
    };
    
    describe("Component", () => {
      it("renders", async () => {
        jest.useFakeTimers();
        const environment = createMockEnvironment();
    
        environment.mock.queueOperationResolver((operation) => {
          return MockPayloadGenerator.generate(operation, {
            DataType() {
              return {
                edges: [
                  {
                    node: {
                      name: "hello",
                    },
                  },
                  {
                    node: {
                      name: "world",
                    },
                  },
                ],
              };
            },
          });
        });
    
        environment.mock.queuePendingOperation(compiledQuery, {
          // these variables need to be identical to the variables used in loadQuery
          id: "testId",
        });
    
        const { getAllByTestId, getByText } = renderWith({ environment });
        act(() => jest.runAllImmediates());
    
        expect(getAllByTestId("list-item").length).toBe(2);
        getByText("hello");
        getByText("world");
      });
    });
    

    【讨论】:

      【解决方案2】:

      我能够通过以下测试通过测试,但我不认为使用间谍是最好的方法。

      
      import { screen, render } from "@testing-library/react"
      import { loadQuery, RelayEnvironmentProvider } from "react-relay"
      import { createMockEnvironment, MockPayloadGenerator } from "relay-test-utils"
      
      //this is only used for the spy
      import * as reactRelay from "react-relay/hooks"
      
      
      test("a list of entries is displayed when the component mounts", async () => {
        const environment = createMockEnvironment()
      
        environment.mock.queueOperationResolver(operation => {
          return MockPayloadGenerator.generate(operation, {
            Entry() {
              return {
                id: "123",
                title: "hello",
                urlKey: "abc"
              }
            }
          })
        })
      
        relay.mock.queuePendingOperation(EntryListQuery, {})
      
        const mockLoadQuery = loadQuery(relay, EntryListQuery, {}, {})
      
        const useQueryLoaderSpy = jest.spyOn(reactRelay, "useQueryLoader").mockReturnValueOnce([null, mockLoadQuery, jest.fn()])
      
        render(<RelayEnvironmentProvider environment={environment}>
                 <Entries />
               </RelayEnvironmentProvider>
        )
      
        expect(await screen.getByText(/hello/i)).toBeInTheDocument()
      
        useQueryLoaderSpy.mockRestore()
      })
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-01-18
        • 2015-12-22
        • 1970-01-01
        • 2020-06-02
        • 1970-01-01
        • 2021-10-24
        • 2018-01-29
        • 1970-01-01
        相关资源
        最近更新 更多