apollo-client: Mocking UseLazyQuery does not return data

Intended outcome: After setting up MockedProvider with query mocks, uselazyquery hook should return mocked data after loading state.

Actual outcome: Data is undefined, and no query results are returned

How to reproduce the issue:

/**
 * @jest-environment jsdom
 */

import {
  render, wait
} from '@testing-library/react';
import { MockedProvider } from '@apollo/react-testing';
import gql from 'graphql-tag';
import React, {useEffect} from 'react'
import { useLazyQuery } from '@apollo/react-hooks';
import '@testing-library/jest-dom/extend-expect';

export const QUERY = gql`
  query BLAH($id: Int) {
    investment(id: $id) {
      id
    }
  }
`;

const Component = () => {
  const 
    [fetchQuery, {loading, error, data, refetch, called }]
   = useLazyQuery(QUERY, {
     variables: {
       id: "suchandsuch"
     }
   });

   useEffect(() => {
    fetchQuery()
   }, [fetchQuery])

  if(loading) return "loading"
  if(data) return "yes dice"
  if(called) return "called"
  return 'no dice'
}

describe("Can UseLazyQuery", () => {
  let component;
  beforeEach(() => {
    const mocks = [
      {
        request: {
          query: QUERY,
          variables: {
            id: "suchandsuch"
          }
        },
        result: {
          data: true
        }
      }
    ]
    component = render(
      <MockedProvider mocks={mocks} addTypename={false}>
        <Component />
      </MockedProvider>
    )
  })

  it("has dice", async () => {
    const { getByText } = component;
    await wait(() => {
      expect(getByText('yes dice')).toBeInTheDocument();
    });
  })
})
 TestingLibraryElementError: Unable to find an element with the text: yes dice. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.

    <body>
      <div>
        called
      </div>
    </body>

      65 |     const { getByText } = component;
      66 |     await wait(() => {
    > 67 |       expect(getByText('yes dice')).toBeInTheDocument();
         |              ^
      68 |     });
      69 |   })
      70 | })

Versions

npx: installed 1 in 1.939s

System: OS: macOS Mojave 10.14.6 Binaries: Node: 12.11.1 - ~/.nvm/versions/node/v12.11.1/bin/node Yarn: 1.22.4 - ~/code/project-mercury/node_modules/.bin/yarn npm: 6.11.3 - ~/.nvm/versions/node/v12.11.1/bin/npm Browsers: Chrome: 84.0.4147.125 Firefox: 77.0.1 Safari: 13.1.2 npmPackages: @apollo/client: ^3.1.3 => 3.1.3 @apollo/react-hooks: ^4.0.0 => 4.0.0 @apollo/react-testing: ^4.0.0 => 4.0.0 apollo-boost: ^0.4.7 => 0.4.7 apollo-cache-inmemory: ^1.6.5 => 1.6.5

Other Mentions I believe these issues were related https://github.com/apollographql/react-apollo/issues/3899 https://spectrum.chat/apollo/testing/how-to-test-uselazyquery~c3a55a74-2a15-4c6d-a0f7-e11966c02479

Hacky Workaround I was playing with

jest.mock('@apollo/react-hooks')
import { useLazyQuery } from '@apollo/react-hooks';

useLazyQuery.mockImplementation((query, {variables: initialVariables}) => {
      const [attributes, setAttributes] = useState({})
      const [mockCount, setMockCount] = useState(0)

      const verifyGraphQLMock = ({variables: passedVariables}) => {
        const variables = passedVariables || initialVariables

        expect(mocks[mockCount]).toBeTruthy()
        expect(query).toEqual(mocks[mockCount].request.query)
        expect(variables).toEqual(mocks[mockCount].request.variables)
        setMockCount(mockCount + 1)
        setAttributes({
          loading: true
        })
        process.nextTick(() => {
          setAttributes(mocks[mockCount].result)
        })
      }

      return [
        verifyGraphQLMock,
        attributes
      ]
    })

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 2
  • Comments: 15

Most upvoted comments

MockedProvider is a real joke really, can’t count the time I lost debugging this crap

I am seeing the same problem with @apollo/client v3.3.20 when using useLazyQuery. Why this issue has come back given that it was already fixed?

If you wanna mock using jest, you use the following approach

jest.mock("apollo-client", () => ({
  __esModule: true,
  useQuery: (query: any) => {
    //other mocks if needed
  },
  useLazyQuery: jest.fn().mockReturnValue([
    jest.fn(),
    {
      data: {
        yourProperties: "Add Here",
      },
      loading: false,
    },
  ]),
}));

As you see, this approach even returns the mock function that is called in the code to be tested.

Your mocking seems to be incorrect, the above test succeeds for me after changing the result mock to

result: { data: { investment: { id: 'HOHO' } } }

See this comment

Here is an example of a component that uses useLazyQuery and a test that tests this component:

import React, { useEffect, useState } from 'react';
import { render, wait, screen } from '@testing-library/react';
import { MockedProvider } from '@apollo/react-testing';
import { gql } from 'apollo-boost';
import { useLazyQuery } from '@apollo/react-hooks';

export const GET_CAR_QUERY = gql`
  query GetCar {
    car {
      make
    }
  }
`;

export function CarLazy() {
  const [fetchCar, { data }] = useLazyQuery(GET_CAR_QUERY);
  const [hasBeenCalled, setHasBeenCalled] = useState(false);

  useEffect(() => {
    if (!hasBeenCalled) {
      setHasBeenCalled(true);
      fetchCar();
    }
  }, [hasBeenCalled, fetchCar]);

  return <p>{data?.car.make || null}</p>;
}

const mocks = [
  {
    request: {
      query: GET_CAR_QUERY,
    },
    result: {
      data: {
        car: { make: 'BMW' },
      },
    },
  },
];

describe('Lazy', () => {
  test('Component test', async () => {
    const { queryByText } = render(
      <MockedProvider mocks={mocks} addTypename={false}>
        <CarLazy />
      </MockedProvider>
    );

    await wait(() => {
      screen.debug();
      expect(queryByText('BMW')).toBeInTheDocument();
    });
  });
});

Hey Guys! It solved when using useLazyQuery in MockedProvider!

I’m using @apollo/client ‘3.5.5’.

And the reason MockedProvider did not work on useLazyQuery was mismatching: ‘variables’, ‘data’. (+ When I used @apollo/client ‘3.3.21’, It does not work.)

In other words, (variables & data) of mocks should be same with (variables & data) of useLazyQuery in real component that you want to test.

For example,

// In real Component,

const [myLazyQuery, myLazyQueryOptions] = useLazyQuery<QueryDataType, QueryVariablesType>(MY_QUERY, {
   variables: {
      ....
   },
    onCompleted: () => { ... }
    ...
})

&

// In test,

const mocks = [
  {
    request: {
       query: MY_QUERY
       variables:  QueryVariablesType   // It should be same!!!
    },
     result: {
        data: QueryDataType  // It should be same!!!
    }
  }
]

...

// You also should set 'false' addTypeName.
// If set 'true', you should declare '__typename' in each mocks data.
<MockedProvider mocks={mocks} addTypeName={false}>
   <YourComponent../>
</MockedProvider>

So I recommend that using with type that declare Mock’s type. (To catch some mistake).

Like this,

export type MockType<TData, TVariables> = {
  request: {
    query: DocumentNode
    variables: TVariables
  },
  result: {
    data: TData
  }
}

...

const myMocks: MockType<QueryDataType, QueryVariablesType>[] = [{ ... }, { ... }]

I hope this would help.

I’m also having this problem. I cannot get respond data from ‘onCompleted’ in useLazyQuery(By MockedProvider). But I found a way mocking useLazyQuery of Apollo package.

import { DocumentNode } from 'graphql'
import * as Apollo from '@apollo/client'
import { LazyQueryHookOptions, NetworkStatus, QueryLazyOptions, QueryTuple, TypedDocumentNode } from '@apollo/client'

export const makeMockedUseLazyQuery = () => Record<string, unknown>) => {
  const mockedUseLazyQuery = jest.spyOn(Apollo, 'useLazyQuery')

  mockedUseLazyQuery.mockImplementation((query: DocumentNode | TypedDocumentNode<unknown, unknown>, 
    options?: LazyQueryHookOptions<unknown, unknown>): QueryTuple<unknown, unknown> => {

    return [
      jest.fn().mockImplementation((_?: QueryLazyOptions<any>) => {
        options?.onCompleted?.({
            users: {
                id: 1,
                name: "mocked!",
                ...
            }
        })
      }),
      {
        loading: false,
        networkStatus: NetworkStatus.ready,
        called: false,
        data: undefined,
      },
    ]
  })

  return mockedUseLazyQuery
}

You can make conditional return data(by switch or if statement) in function’s body. If you want to use respond data by ‘{ data }’(returned by useLazyQuery function), just use return value in second.

Looks like you are right! I see now, will have to take another look at my original implementation.

(Was expecting it to return what ever I set data to)

Thanks!