apollographql / apollo-client

:rocket:  A fully-featured, production ready caching GraphQL client for every UI framework and GraphQL server.
https://apollographql.com/client
MIT License
19.35k stars 2.66k forks source link

MockedProvider does not return mocks in a storybook. #7081

Open MarkLyck opened 4 years ago

MarkLyck commented 4 years ago

Intended outcome: The MockedProvider should work in a storybook environment and return the mock request provided to the MockProvider as per the docs: https://www.apollographql.com/docs/react/api/react/testing/#mockedprovider

Actual outcome: When running a story with MockedProvider, the useQuery hook returns:

{
  error: undefined,
  loading: true,
  data: undefined
}

then it returns:

{
  error: undefined,
  loading: false,
  data: undefined
}

It does not return the mock I provided in my story

How to reproduce the issue:

Here is my story.tsx file

import React from 'react'
import { MockedProvider } from "@apollo/client/testing";
import LatestSells from '~/components/SalesPage/LatestSells'
import { LATEST_SELL_SIGNALS } from '~/common/queries'

export default {
  title: 'Sales page/latest sells',
}

const mocks = [
  {
    request: {
      query: LATEST_SELL_SIGNALS,
      variables: {},
    },
    result: {
      data: {
        dog: {
          name: "Douglas"
        }
      }
    }
  }
]

export const latest_sells = () => {
  return (
    <MockedProvider mocks={mocks} addTypename={false}>
      <LatestSells />
    </MockedProvider>
  )
}

I tried both with and without addTypename={false}

I made my component as simple as possible to troubleshoot, but it doesn't work even when simplified down to the smallest possible react component.

component:

import React from 'react'
import { useQuery } from '@apollo/client'
import { LATEST_SELL_SIGNALS } from '~/common/queries'

const LatestSells = () => {
  const { loading, error, data } = useQuery(LATEST_SELL_SIGNALS)
  console.log(loading, error, data)
  return null
}

export default LatestSells

Even with the simplest possible setup, this does not work.

Lastly here is my query:

import { gql } from 'apollo-boost'

export const LATEST_SELL_SIGNALS = gql`
  query {
    latestSellSignalsList(orderBy: createdAt_DESC, first: 10) {
      items {
        name
        ticker
        boughtAt
        soldAt
      }
    }
  }
`

I've doubled checked that the query console logs correctly and the same in both the story and the actual component.

Versions System: OS: macOS 10.15.6 Binaries: Node: 14.4.0 - /usr/local/bin/node Yarn: 1.22.4 - /usr/local/bin/yarn npm: 6.14.5 - /usr/local/bin/npm Browsers: Chrome: 85.0.4183.121 Firefox: 81.0 Safari: 14.0 npmPackages: @apollo/client: ^3.2.1 => 3.2.1 apollo-boost: ^0.4.9 => 0.4.9 apollo-link: ^1.2.14 => 1.2.14 apollo-link-batch-http: ^1.2.14 => 1.2.14 apollo-link-context: ^1.0.20 => 1.0.20 apollo-link-error: ^1.1.13 => 1.1.13 apollo-utilities: ^1.3.4 => 1.3.4

benjamn commented 4 years ago

@MarkLyck Are you actually still using apollo-boost, apollo-link, apollo-link-batch-http, etc? For the link imports, HttpLink should be imported directly from @apollo/client, and the other links can be imported from @apollo/client/link/core, @apollo/client/link/batch-http, @apollo/client/link/context, and/or @apollo/client/link/error.

benjamn commented 4 years ago

We even have a codemod to help automate the conversion of imports: https://github.com/apollographql/apollo-client/tree/main/scripts/codemods/ac2-to-ac3

akd3vs commented 4 years ago

I have the same issue with @apollo/client/testing I have the mocks but the request data comes undefined.

MarkLyck commented 4 years ago

I find this a strange user-behaviour, but I figured it out.

I was testing it with dummy data this entire time because I just wanted to see the data return "something".

However, apparently if you give the mockProvider a mock that doesn't 100% match the expected schema, it will just give up and return undefined with no error, warning or anything.

When I replaced my dummy data, with a data matching the exact schema of my request it works corrrectly.

Wasted a lot of time on this detail. Please consider adding an error or warning if the mock doesn't match the expected return, if this is the intended behaviour.

P.S. I also tried setting my apollo-link and apollo-boost imports to @apollo-client, but that did not affect anything.

benjamn commented 4 years ago

This behavior should be fixed/improved in @apollo/client@3.3.0-beta.9 (just published to npm), thanks to #7108. Please give it another try after updating!

betofigueiredo commented 4 years ago

Hi @benjamn. I just tried with "@apollo/client": "^3.3.0-beta.9" but had the same problem @MarkLyck said before. Got it working after setting the result data exactly like the schema of my request.

JohannesKlauss commented 3 years ago

@benjamn Can second that. This behavior is still the same with @apollo/client@3.3.0-beta.14. It's actually worse, because apollo doesn't even throw an error anymore when a mock is not found/matched.

tylerbecks commented 3 years ago

I am having this issue right now and it's very difficult to debug what I need to do to return my data. If I change my mocks to use a function to return the result, I can see it's running.

{
    request: {
      query: GET_HOUSE_BY_ID,
      variables: {
        id: PROPERTY_ID,
      },
    },
    result: () => {
      console.log('This runs in my tests but data is undefined')
      return {
        data: {
          houses_by_pk: house,
        }
      }
    }
  },
cgrime01 commented 3 years ago

Having the same issue. As a workaround you may set the fetchPolicy == "no-cache" in the useQuery options:

const { loading, error, data } = useQuery(LATEST_SELL_SIGNALS, {fetchPolicy: "no-cache"})

lukaszkalemba commented 3 years ago

Getting same issue with @apollo/client@3.3.4 even if mocked data matches exactly query inside component.

zeroliu commented 3 years ago

Also found that if the nested mock response does not include __typename, it will also be removed silently.

martimalek commented 3 years ago

I think this may be related.

Regarding addTypename behaviour.

With the following query:

const SIMPLE_QUERY = gql`
    {
        a {
            b
        }
    }
`;

and testing the following hook:

const useSimpleHook = () => {
    const { data, loading, error } = useQuery(SOME_QUERY);

    return { data, loading, error };
};

when addTypename is set to false the hook returns the error:

ApolloError: No more mocked responses for the query: {
    a {
      b
      __typename
    }
  }

I thought from this explanation in the docs that setting addTypename to false would make the __typename property not appear. Instead when setting addTypename to true the test passes successfully and the mock data is returned.

I should also say that the majority of issues during testing had to do with the cache not being reset on every test.

ekremkenter commented 3 years ago

Getting the same issue with @apollo/client@3.3.6 and @apollo/client@3.4.0-beta.4 with installed the dependency of @wry/equality@0.3.1 😞

vtereshyn commented 3 years ago

sorry, any updates here?

JeffDemanche commented 3 years ago

I'm also running into issues getting MockedProvider to do anything. I've looked at the queries for some time and it's always just returning undefined for data with no logging as to what might be wrong with my query. This is with @apollo/client ^3.2.5.

bastianwegge commented 3 years ago

It is like @MarkLyck suggested. If your schema-Mock does not match what the MockedProvider is expecting, it does return undefined. I tried using one of the responses from the browser to generate a Mock and it worked!

ben-gooding-sky commented 3 years ago

Having the same issue. As a workaround you may set the fetchPolicy == "no-cache" in the useQuery options:

const { loading, error, data } = useQuery(LATEST_SELL_SIGNALS, {fetchPolicy: "no-cache"})

I had this exact issue which this fixes, but I don't want to have to refetch my user both on the server and then again in the component, is there another work around for this?

Edit: After a lot of searching there's a work around adding defaultOptions parameter to the mocked provider!

<MockedProvider
        mocks={mocks}
        addTypename={false}
        defaultOptions={{ watchQuery: { fetchPolicy: 'no-cache' } }}
      >
marcin-piechaczek commented 3 years ago

v3.3.11, the same problem. None of the solutions mentioned above helped. Did someone manage to solve this problem differently?

bastianwegge commented 3 years ago

@marcin-piechaczek can you provide a testable Version of what you're working with? I'd love to take a look.

marcin-piechaczek commented 3 years ago

Hi @bastianwegge, thanks for the reply. I noticed that this problem must be somewhere on my side because I cannot reproduce this problem on a mocked project. I will double-check my config and let you know.

bastianwegge commented 3 years ago

@benjamn I guess this issue can be closed then, the original issue-creator was satisfied in September 2020 (https://github.com/apollographql/apollo-client/issues/7081#issuecomment-700923092)

marcin-piechaczek commented 3 years ago

In my case, the SSR turned out to be the problem. I fetch the data on the server-side, then read it from the client-side cache and between the data was undefined. I solved this problem by adding a loading state that satisfies mockProvider.

Stack: Nextjs + apollo + storybook (stories with apollo hook useQuery was undefined). Add loading state to the component and make sure that fetched data is equal to the mocked.

msakrejda commented 3 years ago

Same issue here. A mock call had bit-rotted and broke a story due to a missing query in an updated GraphQL operation. I updated @apollo/client to latest (3.3.13), but still no sensible error message: just loading false, error undefined, data undefined. This is super unhelpful--an error warning of invalid mocks would be much more useful here.

VFC-elindgren commented 3 years ago

Not sure if this is the issue others had, but after hours and hours I finally figured out our issue.

Our mocks were not providing arrays (that were typed as optional), by just adding empty arrays for those properties in the mocks, finally the response is as expected.

a) this seems like a bug b) to echo everyone else, some minimal error message would've been sooooooo helpful here

EDIT: Turns out any prop missing (incl any optional props) is causing our data to be undefined. Our stop gap is to use this type

type RecursiveRequired<T> = T extends Object ? {
  [Property in keyof T]-?: RecursiveRequired<T[Property]>;
} : T

applied to our fragments to enforce that all props are present

natakina commented 3 years ago

I have the same issue with "@apollo/client": "^3.3.15", @ben-gooding-sky Thanks a lot! I spent too much time on this issue. Your solution works great.

I have added defaultOptions={{ watchQuery: { fetchPolicy: 'no-cache' } }} to the MockedProvider and it works. Thank you.

tarehart commented 3 years ago

While fetchPolicy: 'no-cache' will work for most people, I was specifically trying to unit test the caching behavior of my app, so I couldn't use that fix.

Instead, I took the advice from https://levelup.gitconnected.com/gotchas-using-react-apollo-mockprovider-ec2a22a07e76:

Inject InMemoryCache to show the error message

Using that technique, I was able to fix some subtle problems in my mocked responses which had been breaking my test, and I no longer needed to rely on fetchPolicy: 'no-cache'.

natakina commented 3 years ago

@tarehart Thanks for this link. It's very helpful. I have added __typename to the mocks as I use fragments in queries. It's not obvious and there is no mention of this in the docs (or I haven't found).

Dajust commented 3 years ago

I've tried everything on this list, but no success still.

Testing on React Native.

rarenatoe commented 3 years ago

While fetchPolicy: 'no-cache' will work for most people, I was specifically trying to unit test the caching behavior of my app, so I couldn't use that fix.

Instead, I took the advice from https://levelup.gitconnected.com/gotchas-using-react-apollo-mockprovider-ec2a22a07e76:

Inject InMemoryCache to show the error message

Using that technique, I was able to fix some subtle problems in my mocked responses which had been breaking my test, and I no longer needed to rely on fetchPolicy: 'no-cache'.

Solution Number 3 of your link is what fixed it for me. Honestly no idea why I have to do these extra steps since it seems like this should be a part of the processes that the MockedProvider takes, buuuuu...t whatever. Dilemma deleted.

maciejregula commented 3 years ago

This is something which needs to be improved. For example, another scenario is when the MockedQuery should contain variables.

This returns undefined:

const mockedQuery = {
  request: {
    query: MY_QUERY,
    variables: {
      example: "someString",
    },
  },
  .
  .
  .
}

but this works fine:

const mockedQuery = {
  request: {
    query: MY_QUERY,
    variables: {
      example: "",
    },
  },
  .
  .
  .
}

This is ridiculous that "variables" values may cause that the whole data in a test is undefined.

dustin-rcg commented 3 years ago

Please throw an error when mismatched mock shape causes NO MOCK DATA AT ALL to be supplied. That seems like an important requirement for a test component.

leognmotta commented 3 years ago

tried every solution but it does not work

brunolm commented 2 years ago

In my case I had to wait so the request could resolve

import { MockedProvider } from '@apollo/client/testing';
import { act, render, screen } from '@testing-library/react';

beforeEach(async () => {
  render(...)

  await act(async () => {
    await new Promise((resolve) => setTimeout(resolve, 0));
  });
});
avoltis commented 2 years ago

the solution for me without changing cache type on the react component, was to add __typename when needed (where we had a fragment) on the mocks

vladrose commented 2 years ago

I've added __typename field to my mock entities and it works, thanks all!

danielo515 commented 2 years ago

Please consider adding an error or warning if the mock doesn't match the expected return, if this is the intended behaviour.

Indeed throwing, an ideally meaningful, error will be the safest thing to do

jpvajda commented 2 years ago

Just tagging this RFC as it's related https://github.com/apollographql/apollo-client/issues/9738

hesawant commented 1 year ago

Was facing the same issue. I am using @testing-library/react for testing.

The trick is to wrap render inside act(...) and await on it.

import { act, render, screen } from '@testing-library/react';
import { MockedProvider, MockedResponse } from '@apollo/client/testing';

const mocks: MockedResponse[] = [
  {
    request: { ... },
    response: { ... },
  }
]

describe('Test cases', () => {
  it('One of the test case', async () => {
    await act(async () => {                          <<======= THIS LINE
      render(
        <MockedProvider mocks={mocks} addTypename={false}>
          <MyComponent />
        </MockedProvider>
      );
    });

    // Assert on any condition.
    expect(screen.getByTestId(...)).not.toBeNull();
  });
});
malininss commented 11 months ago

I have the same issue (not only in a storybook, but just in my tests). Could anyone from core team pay attention to this issue? It's been 3 years since the bug appeared

For me works two solutions:

  1. Pass defaultOptions={{ watchQuery: { fetchPolicy: 'no-cache' } }} to the MockProvider
  2. Pass prop:
        cache={
          new InMemoryCache({
            possibleTypes: {
              YourInterface: ['possibleTypeOne', 'possibleTypeTwo],
            },
          })
        }