使用 React Relay 测试组件
Posted
技术标签:
【中文标题】使用 React Relay 测试组件【英文标题】:Testing components using 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()
)
【问题讨论】:
【参考方案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()
)
【讨论】:
以上是关于使用 React Relay 测试组件的主要内容,如果未能解决你的问题,请参考以下文章
使用 Jest 对 Relay 容器的集成测试与工作中的 GraphQL 后端不工作
使用 Jest 对 Relay 容器的集成测试与工作中的 GraphQL 后端不工作