Problem

We have a usePaymentEffectReducer that needs to trigger useQuery as a hook:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { useQuery } from '@apollo/client'

const usePairingWithDispatch = (
// arguments
) => {
useQuery<LSPAYPairingData>(GQL_QUERY), {
variables: { ... },
onCompleted: data => {
// Handle data
// dispatch PAIR_OK
},
onError: error => {
// error handling
// dispatch ERROR
},
})
}

In order to test the upper hook, the GraphQL query done by Apollo client needs to be mocked out.

Finding 1: MockedProvider

https://www.apollographql.com/docs/react/v2/api/react-testing/

Example:

1
2
3
4
5
6
7
8
9
it('runs the mocked query', () => {
render(
<MockedProvider mocks={mocks}>
<MyQueryComponent />
</MockedProvider>
)

// Run assertions on <MyQueryComponent/>
})

New Problem: I don’t need a component, I need to render a hook

I cannot just do:

1
2
3
4
5
6
7
const { result, waitForNextUpdate } = renderHook(() =>
<MockedProvider>
usePaymentEffectReducer({
...TEST_ARGS,
})
</MockedProvider>
)

Finding 2:

https://react-hooks-testing-library.com/usage/advanced-hooks#context

Often, a hook is going to need a value out of context. The useContext hook is really good for this, but it will often require a Provider to be wrapped around the component using the hook. We can use the wrapper option for renderHook to do just that.

Example:

1
2
3
4
const wrapper = ({ children }) => (
<CounterStepProvider step={2}>{children}</CounterStepProvider>
)
const { result } = renderHook(() => useCounter(), { wrapper })

New problem: mocking graphQL error in mocks gets type error

1. Specify graphQL error as a field of result

https://github.com/apollographql/react-apollo/issues/1127

2. use GraphQLError constructor from graphql library

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { GraphQLError } from 'graphql'
mocks={[
{
request: APOLLO_REQUEST,
result: {
errors: [
new GraphQLError(
'pairing missing',
[],
undefined,
[],
[],
undefined,
{ code: SWIPER_ERROR_CODES.CODE_REGISTER_PAIRING_NOT_FOUND }
),
],
},
},
]}

New problem: wrong act?

Symptoms

I imported act together with renderHook, thinking that would make sure my act will be the correct one:

1
import { renderHook, act } from '@testing-library/react-hooks'

The code where I used act looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
const { result } = renderHook(() => usePaymentEffectReducer(TEST_ARGS), {
wrapper: getApolloWrapperWithPairings([TEST_PAIRING_DATA]),
})
await waitFor(() => {
expect(result.current[0].status).toBe('error')
})

act(() => {
result.current[1]({
type: 'RETRY',
previousState: COLLECTING_STATE as PaymentState,
})
})

However, I still get an error message like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Warning: It looks like you're using the wrong act() around your test interactions.
Be sure to use the matching version of act() corresponding to your renderer:

// for react-dom:
import {act} from 'react-dom/test-utils';
// ...
act(() => ...);

// for react-test-renderer:
import TestRenderer from 'react-test-renderer';
const {act} = TestRenderer;
// ...
act(() => ...);
in TestComponent
in ApolloProvider (created by MockedProvider)
in MockedProvider
in Unknown
in Suspense
in ErrorBoundary

Research on act doc

According render-hooks/act:

This is the same act function function that is exported from your chosen renderer.

Which of the two renderers will I import by importing react-hooks?

When using standard import for this library (show below), we will attempt to auto-detect which renderer you have installed and use it without needing any specific wiring up to make it happen. If you have both installed in your project, and you use the standard import (see below) the library will default to using react-test-renderer.

We use react-test-renderer by default as it enables hooks to be tested that are designed for either react or react-native and it is compatible with more test runners out-of-the-box as there is no DOM requirement to use it.

The interesting thing is that the wrapper will use dom renderer, so if act use the native renderer imported by auto detection, there will be a mismatch.

If I import specifically dom renderer like this:

1
import { renderHook, act } from '@testing-library/react-hooks/dom'

The mismatch will be resolved.