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

MockedProvider doesn't log error when mock is missing, and provides no way to inspect the queries being made during a test #5917

Closed
bobbybobby opened this issue Feb 6, 2020 · 21 comments · Fixed by #10502
Assignees
Labels
🚧 in-triage Issue currently being triaged 🔍 investigate Investigate further

Comments

@bobbybobby
Copy link

bobbybobby commented Feb 6, 2020

Hi ! I came across the following scenario several time when developping unit tests with MockedProvider, and I think some features could make it easier and more efficient to debug the tests.

Sometimes, the mocked queries I wrote don't match the queries actually made by the component (either a typo when creating the mock, or the component evolves and changes some query variables). When this happens, MockedProvided returns the NetworkError Error: No more mocked responses for the query to the component. However in the test suite, no warning is displayed. This is frustrating, because sometimes my components do nothing with the error returned by the client, thus it goes unnoticed. This cause my tests, which used to pass, to suddenly fail silently, and gives me a hard time to find the cause.

This is an example of component using useQuery :

import React from 'react';
import {gql} from 'apollo-boost';
import {useQuery} from '@apollo/react-hooks';


export const gqlArticle = gql`
  query Article($id: ID){
    article(id: $id){
      title
      content
    }
  }
`;


export function MyArticleComponent(props) {

  const {data} = useQuery(gqlArticle, {
    variables: {
      id: 5
    }, 
  });

  if (data) {
    return (
      <div className="article">
        <h1>{data.article.title}</h1>
        <p>{data.article.content}</p>
      </div>
    );
  } else {
    return null;
  }
}

And this is a unit test, in which I made a mistake, because the variables object for the mock is {id: 6} instead of {id: 5} which will be requested by the component.

  it('the missing mock fails silently, which makes it hard to debug', async () => {
    let gqlMocks = [{
      request:{
        query: gqlArticle,
        variables: {
          /* Here, the component calls with {"id": 5}, so the mock won't work */
          "id": 6,
        }
      },
      result: {
        "data": {
          "article": {
            "title": "This is an article",
            "content": "It talks about many things",
            "__typename": "Article"
          }
        }
      }
    }];

    const {container, findByText} = render(
      <MockedProvider mocks={gqlMocks}>
        <MyArticleComponent />
      </MockedProvider>
    );

    /* 
     * The test will fail here, because the mock doesn't match the request made by MyArticleComponent, which
     * in turns renders nothing. However, no explicit warning or error is displayed by default on the console,
     * which makes it hard to debug
     */
    let titleElement = await findByText("This is an article");
    expect(titleElement).toBeDefined();
  });

To me, it seems that most of the time, this error would not be intentional, and that the expected behavior isn't to send it to the component as a real world error, but instead would be to display a warning in the unit test. I understand the error being sent to the component, but I think MockedProvider should also provide a warning by default (which could be configured to be turned off), and/or an easy way to instrospect what's going on with the queries and mocks happening during the test.

I put some stuff together to achieve what I want, but I suggest this be part of the functionality offered by MockedProvider :

Log the error "no more mocked responses for the query" by default

I chained the MockLink with an apollo error link in order to catch the error and log it.

import React from 'react';
import {MockedProvider} from '@apollo/react-testing';
import {MockLink} from '@apollo/react-testing';
import {onError} from "apollo-link-error";
import {ApolloLink} from 'apollo-link';

export function MyMockedProvider(props) {
  let {mocks, ...otherProps} = props;

  let mockLink = new MockLink(mocks);
  let errorLoggingLink = onError(({ networkError }) => {
    if (networkError) {
      /No more mocked responses for the query/.test(networkError.message);
      console.warn(`[Network error]: ${networkError}`);
    }
  });
  let link = ApolloLink.from([errorLoggingLink, mockLink]);

  return <MockedProvider {...otherProps} link={link} />;
}

Provide a way to inspect which queries are made to the MockLink

I wanted a way to be sure which queries where made by the component I was testing, in order to understand why it didn't use the mock I provided. The only way I found to make this was to extend MockLink to put a spy in it.

import React from 'react';
import {MockedProvider} from '@apollo/react-testing';
import {MockLink} from '@apollo/react-testing';
import {onError} from "apollo-link-error";
import {ApolloLink} from 'apollo-link';

class MyMockLink extends MockLink {
  request(operation) {
    console.log(operation);
    return super.request(operation);
  }
}

export function MyMockedProvider(props) {
  let {mocks, ...otherProps} = props;

  let mockLink = new MyMockLink(mocks);

  return <MockedProvider {...otherProps} link={mockLink} />;
}

I don't know how it could be designed, but I would find nice to have a way to log or inspect what is going on with the query, in the spirit of this snippet.

@flo-sch
Copy link
Contributor

flo-sch commented Aug 24, 2020

Quite interested by this idea as well, I have been struggling with the same issues for a while.

So far, I can (almost) resume all of those warnings to 2 cases (from my own experience):

  • missing mock(s) for a given query
  • variables difference (usually, when variables are computed from values provided by a React context, and the mocked context is not updated)

I would find it amazing if the Mocked provider could notice such cases (to start with):

  1. if there is no mock at all for a given query
    comparing the operation's operationName perhaps?
  2. if there is a difference in query variables
    probably less easy to pin-point, since in theory there could be several mocked queries with different variables, especially when testing high-level components embedding many queries?
    one way I could think about would be to JSON.stringify() the variables of the query being sent, and an array of the mocked variables for the same operation (still by its name?)

@lukewlms
Copy link

lukewlms commented Sep 7, 2020

I just solved a related issue in our code, but in this case no errors were present at all, either in console or in the errors object returned by the query.

Prior to loading the state is as expected (data and errors undefined, loading true).

But after loading the state is totally unexpected (data and errors still undefined, loading false).

// Fails silently (wrong shape when passed as mock data)
const analyticsMockData: AnalyticsQuery_analytics = {
    __typename: "Analytics",
    countOfOpenInvestments: 100,
    totalValuationAmount: 35_000_000_00,
    trailingPaymentsTotal: 20_000_000_00,
};

// Works correctly
const analyticsMockData: AnalyticsQuery = {
  analytics: {
    __typename: "Analytics",
    countOfOpenInvestments: 100,
    totalValuationAmount: 35_000_000_00,
    trailingPaymentsTotal: 20_000_000_00,
  },
};

const analyticsLevelMocks: MockedProviderProps["mocks"] = [
  {
    request: { query: analyticsQuery, variables: analyticsMockVariables },
    result: { data: analyticsMockData },
  },
];

@just-Bri
Copy link

just-Bri commented Oct 26, 2020

I'm having a, I think, similar problem. In my component I have:

  const { user } = useContext(UserContext);
  const { data: taskData, loading } = useGetAssignedTasksQuery({
    variables: {
      // id: [user?.id],
      // the following line is needed for testing, trying to make this work without it.
      id: [user?.id || 'b95da0bb-8652-4c61-bbf1-4d9388d190aa'],
    },
  });

In my mock data I supply a variable:

const mocks = [
  {
    request: {
      query: GetAssignedTasksDocument,
      variables: {
        id: ['b95da0bb-8652-4c61-bbf1-4d9388d190aa'],
      },
    },
    result: {...}
  },
];

The id variable in the mock is being ignored. If I call my component as above, passing in user.id || 'id string' the test passes.
If I just assign id: [user.id] in the component I get this error:

Error: Uncaught [Error: No more mocked responses for the query: query GetAssignedTasks($id: [ID!]!)
variables: {"id":[null]}

@TomasB
Copy link

TomasB commented Mar 30, 2021

Please bump the priority of this issue. This issue would save way more time for developers than whatever feature you have planned for the next release.

@brainkim brainkim self-assigned this Mar 30, 2021
@brainkim brainkim added the 🚧 in-triage Issue currently being triaged label Mar 30, 2021
@jmvtrinidad
Copy link

This used to work in version 2 and also another behavior that did not work anymore is when the query added a new field then the mock did not include that field, apollo client will return a warning.

@GhaythFuad
Copy link

This has literally wasted half of my day today, a simple error in the query syntax that's easily discoverable on the playground can waste a lot of your time trying to figure it out! (when you don't have an accessible playground)

@lhguerra
Copy link

2. sy t

That helped me find my problem, and I'll explain for the next travelers that might have the same issue as me:

One argument for my mocked query should also be mocked, because in the component it was coming from React Router's useParams hook.

@nathanredblur
Copy link

This will be so useful, please consider do this improvement.

@ryanyue123
Copy link

+1

@RichMatthews
Copy link

still an issue, please release a fix

@TomasB
Copy link

TomasB commented Feb 8, 2022

@brainkim Any progress ? It's going to be a year soon since you have assigned this issue to yourself.

@Daavidaviid
Copy link

Daavidaviid commented Feb 8, 2022

I can confirm, this is such a pain in the *ss, if the MockedProvider could have one single onError props to get a callback called everytime a query/mutation issue is encountered it could be nice.

@aturkewi
Copy link

aturkewi commented Feb 9, 2022

I've lost hours to this in development as well. It can take some time before realizing that this is the problem and then needing to track down which of the many fields is missing. ++ I would love to get an error when a mock doesn't match.

@TheMightyHoltzman
Copy link

Spent a couple of hours today looking for an error in my mocked queries. An onError like @Daavidaviid suggested would help tremendously

@juliaschiller150
Copy link

Just leaving my two cents. I would love to see additional DX investment in MockedProvider. I recently led an effort to revamp the way we mocked our unit tests - updating 650+ individual mocks - and it was incredibly difficult to determine what MockedProvider had in memory for any given test. Lots of trial and error and guessing.

@hitoshisatow
Copy link

hitoshisatow commented Apr 7, 2022

I've spent a day trying to figure out why I'm getting undefined when I specify the mock request/response. More information in the documentation would be helpful in debugging the MockedProvider. I've tried creating my own link and I see it the constructor is called but I don't see any logging occurring inside the request method (ie. it doesn't look like it's being called.) Would love to see more capabilities for debugging what's inside the MockedProvider so I can know what I am doing wrong

@hwillson hwillson added the 🔍 investigate Investigate further label May 31, 2022
@dcworldwide
Copy link

Also faced this issue today.

We are actually using MockLink and MockedProvider in Production (live demo site disconnected from server with fake data).

Some challenges:

  • This issue. Tried to solve it with a custom Link but it only fires if a mocked query matches the query used within the application
  • MockLink wasn't documented alongside MockedProvider
  • I would prefer if the mocks passed into MockLink and MockedProvider, was actually a function, that gave query context (query and variables). This way I could customise the response dynamically based on the variables. We have a lot of queries where variables are timestamp based... makes mocking difficult.

@mlarcher
Copy link

I believe we should avoid the pitfalls of MockedProvider altogether and use a more reliable approach as per https://techblog.assignar.com/dos-and-donts-of-testing-apollo/#do-mock-at-the-service-worker-level-

omarkohl added a commit to cleodora-forecasting/cleodora that referenced this issue Jan 1, 2023
This way it can be re-used as recommended here:

https://techblog.assignar.com/dos-and-donts-of-testing-apollo/

It's an attempt to avoid using Apollo MockedProvider, which seems to be
a real pain (e.g.
apollographql/apollo-client#5917).
@jerelmiller
Copy link
Member

Hey all 👋 !

Thanks for you patience with this issue! I agree with a lot of what has been said here. To be totally honest, I was pretty surprised to find out we didn't already issue a warning in the console when no mocks are matched. I definitely agree this would provide a bit better experience since its typically user error when this occurs. #10416 was opened to provide a potential solution to this issue. I've asked for a few tweaks to that particular solution, but I'm hoping we can provide something here very soon.

On a separate note, we do plan to revisit our testing approach (#9738). We realize there are a lot of shortcomings of the current solution as apps scale and we'd like to provide some better tooling to help. Keep an eye on that issue for updates and feel free to provide any ideas you have there!

@jerelmiller
Copy link
Member

jerelmiller commented Feb 3, 2023

Hey all 👋 !

#10502 has just been merged which will now log a warning to the console if a query cannot be matched against an existing mock. This will go out with the next patch release 3.7.7 (likely tomorrow or early next week). Hope this small improvement helps your testing workflow!

@github-actions
Copy link
Contributor

github-actions bot commented Mar 5, 2023

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.
For general questions, we recommend using StackOverflow or our discord server.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Mar 5, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
🚧 in-triage Issue currently being triaged 🔍 investigate Investigate further
Projects
None yet