Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mocking UseLazyQuery does not return data #6865

Closed
Scong opened this issue Aug 19, 2020 · 15 comments
Closed

Mocking UseLazyQuery does not return data #6865

Scong opened this issue Aug 19, 2020 · 15 comments

Comments

@Scong
Copy link

Scong commented Aug 19, 2020

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
apollographql/react-apollo#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
      ]
    })
@vipul0092
Copy link

vipul0092 commented Aug 26, 2020

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

@Scong
Copy link
Author

Scong commented Aug 26, 2020

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!

@Scong Scong closed this as completed Aug 26, 2020
@abhayshiravanthe
Copy link

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.

@onichandame
Copy link

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?

@cjl-es
Copy link

cjl-es commented Jun 28, 2021

@onichandame I'd agree, I'm also seeing this same issue with useLazyQuery.
I'd like to see the issue get re-opened so it can be addressed. The idea of using Jest.mock for Apollo Client is fine in general, but this isn't ideal in all scenarios.

@BearHunter49
Copy link

BearHunter49 commented Jul 5, 2021

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.

@vipul0092
Copy link

@onichandame
There was no issue here in the first place.
I'm using the latest apollo client and we have numerous tests that test useLazyQuery using MockedProvider and they all work fine. (some of them even test the onCompleted method)

If your tests are not working correctly, that means your mocked responses that you feed into MockedProvider are incorrect.

@cjl-es
Copy link

cjl-es commented Jul 12, 2021

@vipul0092 do you have a resource that shows examples of a useLazyQuery response? The Apollo docs are somewhat lacking in this area (for useLazyQuery anyways)

In my case I'm using both in various parts of code, but the useQuery has no issues when the MockedProvider attempts to return the data. Given that I created the return data object the same way for both useQuery and useLazyQuery (even went as far as to copy + paste the version from useQuery that was working to double check myself) I would expect to have no issues.

At the very least it would be helpful for the mocked provider to throw warnings if it's unable to respond to a request based on return data formatting.

@BearHunter49
Copy link

BearHunter49 commented Dec 1, 2021

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.

@camilocastro0119
Copy link

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.

Full example please!

@Geckoff
Copy link

Geckoff commented Jan 27, 2022

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();
    });
  });
});

@swimhiking
Copy link

swimhiking commented May 9, 2022

not working for me; not sure because I have 2 consequence useLazyQuery? second one depends on first one; but both return null; I use apollo client 3.5.8; thanks

@zerubeus
Copy link

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

@swimhiking
Copy link

Actually MockedProvider is working, the tricky is you need "Your test must execute an operation that exactly matches a mock's shape and variables to receive the associated mocked response.", I guess this is in the document; So that means when you define your Apollo client query, make sure you have defined type for response data.

@NevetsKuro
Copy link

Try adding a console.log inside your onError block in the real component. It will notify you what your shape of the object is missing.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Feb 1, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests