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

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.

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 104
  • Comments: 21 (4 by maintainers)

Commits related to this issue

Most upvoted comments

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.

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)

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 (https://github.com/apollographql/apollo-client/issues/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!

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.

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!

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.

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.

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

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

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

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]}

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 },
  },
];