'Is there any solution to mock react-query's useQuery and useMutation while working with react testing library

I'm working on test cases with react testing library. To write the test case I need to mock the useQuery and useMutation method of react query. If anyone knows the solution please guide me. I'm adding the related code here.

WorkspaceDetailsSection.test.tsx

import React from 'react'
import '@testing-library/jest-dom'
import '@testing-library/jest-dom/extend-expect'
import { screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { WorkspaceDetailsSection } from '../WorkspaceDetailsSection'
import { render } from '../../_utils/test-utils/test-utils'

const mockedFunction = jest.fn()

let realUseContext: any
let useContextMock: any

// Setup mock
beforeEach(() => {
  realUseContext = React.useContext
  useContextMock = React.useContext = jest.fn()
})
// Cleanup mock
afterEach(() => {
  React.useContext = realUseContext
})

jest.mock('../data', () => ({
  useMutationHook: () => ({ mutate: mockedFunction })
}))

const WorkspaceContext = {
  workspaceInfo: {
    name: 'name',
    dot: 'name',
    type: 'type'
  }
}
test('renders section with the valid details', async () => {
  useContextMock.mockReturnValue(WorkspaceContext)

  render(<WorkspaceDetailsSection />)

  expect(screen.getByText('Workspace name:')).toBeInTheDocument()
})

WorkspaceDetailsSection.tsx

import React, { useContext } from 'react'

import { FormModal, useDisclosure } from '@chaine/keychaine'
import { workspaceService } from './data'
import { useMutation } from 'react-query'
import { Section } from '../account-settings'
import { Toast } from '../_shared/components'
import { IWorkspace } from '../_shared/refactoredInterfaces'
import constant from '../_shared/constants/message'
import { WorkspaceContext } from './WorkspacePage'
import capitalizeFirstLetter from '../_utils/capitalizeFirstLetter'
import { queryClient } from '../_shared/infra'
/**
 *
 * should import section component and return that
 */
export const WorkspaceDetailsSection = () => {
  const { isOpen, onOpen, onClose } = useDisclosure()
  const { workspaceInfo } = useContext(WorkspaceContext)
  const { name, dot, type } = workspaceInfo

  const { showToast } = Toast()

  const updateWorkspace = useMutation((params: any) => workspaceService.updateWorkspace(params))

  const handleSubmit = async (event: any) => {
    const params: IWorkspace = {
      ...event,
      id: workspaceInfo.id,
      type: event.type.value
    }

    updateWorkspace.mutate(params, {
      onSuccess: () => {
        onClose()
        queryClient.invalidateQueries('WorkspaceProfile')
      },
      onError: () => {
        showToast({
          title: constant.UNABLE_TO_UPDATE_WORKSPACE,
          status: 'error'
        })
      }
    })
  }

  return (
    <>
      <Section
        sectionTitle={'Workspace details'}
        multipleFields={[
          { fieldName: 'Workspace name:', value: name },
          { fieldName: 'Workspace type:', value: type },
          { fieldName: 'DOT #:', value: dot }
        ]}
        buttonName={'Edit details'}
        onClick={() => onOpen()}
      />

      <FormModal
        isOpen={isOpen}
        onClose={() => onClose()}
        title={'Change workspace info'}
        size={'lg'}
        formSubmitHandler={handleSubmit}
        isLoading={updateWorkspace.isLoading}
        initialValues={{
          dot: dot,
          type: { label: capitalizeFirstLetter(type), value: type },
          name: name
        }}
        modalItems={[
          {
            name: 'name',
            type: 'input',
            placeHolder: 'Enter you name',
            labelText: 'Workspace display name'
          },
          { name: 'dot', type: 'input', placeHolder: 'Enter you DOT #', labelText: 'DOT #' },
          {
            name: 'type',
            type: 'select',
            placeHolder: type,
            labelText: 'Workspace type',
            selectOptions: [
              { label: 'Carrier', value: 'carrier' },
              { label: 'Broker', value: 'broker' },
              { label: 'Shipper', value: 'shipper' }
            ]
          }
        ]}
      />
    </>
  )
}

WorkspaceService.ts

import { BaseAPI } from '../../_shared/infra/services/BaseAPI'
import { ITokenService, IWorkspace } from '../../_shared/refactoredInterfaces'
import { TeamResponseDTO, UpdateWorkspaceResponseDTO, UploadWorkspaceLogoResponseDTO } from './WorkspaceDTO'

export interface IWorkspaceService {
  getWorkspace(): Promise<TeamResponseDTO>
  updateWorkspace(params: IWorkspace): Promise<UpdateWorkspaceResponseDTO>
  uploadWorkspaceLogo(params: any): Promise<UploadWorkspaceLogoResponseDTO>
}

export class WorkspaceService extends BaseAPI implements IWorkspaceService {
  constructor(tokenService: ITokenService) {
    super(tokenService)
  }

  async getWorkspace(): Promise<TeamResponseDTO> {
    const body = {
      url: '/users/account'
    }

    const { data } = await this.get(body)
    return {
      team: data?.data,
      message: data?.message
    }
  }

  async updateWorkspace(params: IWorkspace): Promise<UpdateWorkspaceResponseDTO> {
    const body = {
      url: '/accounts',
      data: params
    }
    const { data } = await this.put(body)
    return {
      message: data.message
    }
  }

  async uploadWorkspaceLogo(params: FormData): Promise<UploadWorkspaceLogoResponseDTO> {
    const body = {
      url: '/accounts/logos',
      data: params,
      headers: {
        'Content-Type': 'multipart/form-data'
      }
    }
    const response = await this.post(body)

    return response
  }
}

Also tried the solution proposed by @TkDodo here but did not work for me. The solution of this will be a life saver for me so thank you folks in advance.



Solution 1:[1]

I don't have enough reputation to reply to @TkDodo's comment above (the "price" of mostly lurking around on SO, I suppose ?), but I have found that mocking react-query's useQuery() return value is definitely useful for exercising specific rendering outcomes without having to bother with async tests littered with await waitFor()s all over.

Sadly, I have not been able to find a ready-made package that manages this for me.

That said, I can report that I've found value in having created a few helper functions for my unit tests that produce an object with the de-structured keys documented here.

The function signatures are as follows:

export function generateUseQueryReturnValueError(error, overrides = {}) {
  // use the error argument as the reported error object and make sure to
  // set flags like `isSuccess`, `isError`, `isLoading` and the rest...
}

export function generateUseQueryReturnValueSuccess(data, overrides = {}) {
  // use the data argument as you'd expect and, obviously, set the flags
}

export function generateUseQueryReturnValueLoading(overrides = {}) {
  // same note about setting the flags correctly
}

It's worth nothing, however, that although these functions do some (very light!) "sanity" checking / prevention, like ensuring that the isError field cannot be overridden to false for generateUseQueryReturnValueError(), there's obviously plenty of room for outcomes that are "impossible" in "real life".

Solution 2:[2]

Yes there is a way to mock useQuery data by intercepting the endpoint. you can follow this example: https://github.com/MitchelSt/react-course/tree/testing/rtl/react-query

or this video: https://www.youtube.com/watch?v=rGK2KiP9a5Y

Solution 3:[3]

If you want to mock a method that returns a useQuery, you should do the following: For example we have a hook "useExample" that return an useQuery:

    export const useExample = (props?: ResponseType): UseQueryResult<ResponseModel> =>  
 useQuery<ResponseModel>(
     "get-example",
     () => getExample(),
     {
       ...props,
       select: (data) => data,
     }   );

the mock:

const useExampleMock = (params: UseQueryResult<ResponseModel>) =>
  jest.spyOn(useConfigModule, 'useExample').mockImplementation(() => {
    return params;
  });

using this mock:

export const MOCK_USE_QUERY_RESULT = {
  data: undefined,
  dataUpdatedAt: 0,
  error: null,
  errorUpdatedAt: 0,
  failureCount: 0,
  isError: false,
  isFetched: false,
  isFetchedAfterMount: false,
  isFetching: false,
  isIdle: false,
  isLoading: false,
  isLoadingError: false,
  isPlaceholderData: false,
  isPreviousData: false,
  isRefetchError: false,
  isRefetching: false,
  isStale: false,
  isSuccess: true,
  status: ‘success’,
  refetch: jest.fn(),
  remove: jest.fn(),
};

    useConfigMock({...MOCK_USE_QUERY_RESULT, isError: false, isSuccess: false});

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 prometheas
Solution 2 Vujicic Nikola
Solution 3 ppaulino