3

(This is somewhat related to this three year-old post. I have tried to to follow the answers but they did not resolve my problem.)

I want to build a general mock (spy) navigator that I can pass in like I do here with spyNavigation:

  it('shows an error if the request fails', async () => {
    const spyNavigation = {
      replace: jest.fn(),
      push: jest.fn(),
    }
    mockServer.use(
      rest.get('*/users/current-user', (req, res, ctx) => {
        return res(ctx.status(200), ctx.json({ id: 1 }))
      })
    )
    mockServer.use(
      rest.get('*/some-data', (req, res, ctx) => {
        return res(ctx.status(500))
      })
    )

    // @ts-ignore
    TestHelper.renderWithReactQueryClient(<OverviewScreen navigation={spyNavigation} />)

    await TestHelper.waitForWithExtendedTimeOut(() => {
      expect(spyNavigation.push).toHaveBeenCalledWith('Error', {
        error: new Error('Request failed with status code 500'),
      })
    })
  })

I would like to abstract the spyNavigation to a test helper and have TypeScript help me out to Partial typing it. So it will code-complete methods like 'replace', 'push' etc.

The TypeScript of react-navigation is just so complex for me to understand. It would be fine if the spy does not know about the names Screens/Navigators - just to be a general "navigation" object.

import { BottomTabScreenProps } from '@react-navigation/bottom-tabs'
import { CompositeScreenProps, NavigatorScreenParams } from '@react-navigation/native'
import { NativeStackScreenProps } from '@react-navigation/native-stack'
import { AxiosError } from 'axios'

declare global {
  namespace ReactNavigation {
    interface RootParamList extends RootStackParamList {}
  }
}

export type RootStackParamList = {
  SignInOrCreateUser: undefined
  SignIn: undefined
  Root: NavigatorScreenParams<RootTabParamList> | undefined
  Modal: undefined
  Error: { error: AxiosError | null } | undefined
}

export type RootTabParamList = {
  Overview: undefined
  Returns: undefined
  Portfolio: undefined
  More: undefined
}

export type RouteName = keyof RootStackParamList | keyof RootTabParamList

export const routeTypeGuard = (route: RouteName) => route

export type RootStackScreenProps<Screen extends keyof RootStackParamList> = NativeStackScreenProps<
  RootStackParamList,
  Screen
>

export type RootTabScreenProps<Screen extends keyof RootTabParamList> = CompositeScreenProps<
  BottomTabScreenProps<RootTabParamList, Screen>,
  NativeStackScreenProps<RootStackParamList>
>

this does not work

import { NativeStackNavigatorProps } from '@react-navigation/native-stack/lib/typescript/src/types'

export const mockNavigation: NativeStackNavigatorProps = {
  navigate: jest.fn(),
  push: jest.fn(),
  replace: jest.fn(),
  pop: jest.fn(),
}
Type '{ navigate: jest.Mock<any, any>; push: jest.Mock<any, any>; replace: jest.Mock<any, any>; pop: jest.Mock<any, any>; }' is not assignable to type 'NativeStackNavigatorProps'.
  Object literal may only specify known properties, and 'navigate' does not exist in type 'NativeStackNavigatorProps'.ts(2322)

I have a guess that it should be something like

export const mockNavigation: Record<keyof ???, () => ReturnType<typeof jest.fn>> = {
  navigate: jest.fn(),
  push: jest.fn(),
  replace: jest.fn(),
  pop: jest.fn(),
}
Norfeldt
  • 8,272
  • 23
  • 96
  • 152
  • Hi. Hope I'm not missing something, but have you tried typing with `StackNavigationProp` type? – rexess May 20 '22 at 08:23
  • added that attempt to the question - it does not work @rexessilfie – Norfeldt May 20 '22 at 08:27
  • guessing it should be something with `mockNavigation: Record ReturnType> ` – Norfeldt May 20 '22 at 08:30
  • Yep. That approach seems like it should work! I will post an answer attempting to fill in the types for the ????, and let me know if it works. It still uses `StackNavigationProp` which looks to have the functions you are looking to mock. – rexess May 20 '22 at 20:37

1 Answers1

1

Here is a potential solution to your question after your comments! I hope this works for you. I am extending the StackNavigationProp which has the names of the functions you want: navigate, push etc. Also from your question, I am replacing the types of its properties to be the return of jest.fn.

The optional generics for MockStackNavigationProp, should also mirror those of StackNavigationProp, allowing you to specify if ParamList if available.

If this works, I wonder if it could be taken further to also have the argument types of the jest function mocks, be automatically inferred from the original function definitions.

import { ParamListBase } from "@react-navigation/native";
import { StackNavigationProp } from "@react-navigation/stack";

type MockStackNavigationProp<
  ParamList extends ParamListBase = any,
  RouteName extends keyof ParamList = string
> = Record<
  keyof StackNavigationProp<ParamList, RouteName>,
  ReturnType<typeof jest.fn>
>;

const mockNavigation: MockStackNavigationProp = {
  navigate: jest.fn(),
  pop: jest.fn(),
  push: jest.fn()
  //.. and more, OR use Partial<MockStackNavigationProp>
};

CodeSandbox link.

rexess
  • 729
  • 4
  • 7