I am using the new Relay Hooks and finding it difficult to get the tests passing. I am experiencing the issue mentioned in their docs.
If you add the console.log before and after usePreloadedQuery, only the "before" call is hit
//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 }
I am finding that the loadQuery
is getting called, however anything I console.log
in queueOperationResolver
doesn't appear. And If I add a console.log
before usePreloadedQuery
it outputs, however after it doesn't. So it appears the EntryList
is being suspended and the query is never resolving.
I have found that if I change the test to the below is also doesn't trigger any errors, it looks like queueOperationResolver
never gets called.
environment.mock.queueOperationResolver(() => new Error("Uh-oh"))
When I console.log
the queryReference
in the EntryList
before the usePreloadedQuery
code it outputs an object such as the below. So I know that the query is being passed down correctly.
{
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)]
},
...
Update
I have found that the following tests works, so this means that there is something I am doing wrong when trying to mock the query in the component that uses 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()
})
I was able to get the test passing with the following tests, however I don't feel like using a spy is the best approach.
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()
})