使用React Relay测试组件



我正在使用新的中继挂钩,发现很难通过测试。我遇到了他们文档中提到的问题。

如果在使用预加载查询之前和之后添加console.log,则只有;在";呼叫命中

//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中的任何Iconsole.log都没有出现。如果我在usePreloadedQuery之前加一个console.log,它就会输出,但在它之后就不会了。因此,EntryList似乎被挂起,并且查询永远无法解析。

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

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

当我在usePreloadedQuery代码之前的EntryList中的console.logqueryReference时,它输出一个对象,如下面所示。所以我知道这个查询是正确传递的。

{
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()
})

我通过了以下测试,但我觉得使用间谍不是最好的方法。


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()
})

感谢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");
});
});

最新更新