最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Apollo MockedProvider always returns undefined for data field, even after loading - Stack Overflow

programmeradmin3浏览0评论

Some background: I've got a ponent that immediately calls a useQuery hook upon loading. While that query is running, I spin a loading spinner. Once it pletes I render stuff based on the data.

I've added a useEffect hook that watches the result of the query and logs the data, which is how I observed this issue.

To simplify things, it works like this:

export default function MyComponent(props: ???) {
    const result = useQuery(INITIAL_DATA_QUERY, { variables: { id: 1 } });

    React.useEffect(() => console.log(JSON.stringify({
        loading: result.loading,
        data: result.data,
        error: result.error,
    })), [result]);

    if (result.loading) return <LoadingScreen message="Fetching data..."/>;
    else if (result.error) return <ErrorPage/>
    else return <Stuff info={result.data}> // omitted because it's unimportant to the issue
}

When I run this ponent in the wild, everything works exactly as expected. It hits the endpoint with GraphQL through Apollo, makes rendering decisions based on the result, etc.

When I try to mock the request out though, the result.data and result.error fields never change, even though the result.loading field does. I am using react-testing-library to run the tests.

My tests look like this:

it("should load the data then render the page", () => {

    const mocks = [{
        request: {
            query: INITIAL_DATA_QUERY,
            variables: { id: 1 },
        },
        newData: jest.fn(() => ({
            data: {
                firstName: "Joe",
                lastName: "Random",
            }
        }))
    }];

    const mockSpy = mocks[0].newData;

    render(
        <MockedProvider mocks={mocks} addTypename={false}>
            <MyComponent/>
        </MockedProvider>
    )

    // Is it a loading view
    expect(result.asFragment()).toMatchSnapshot(); // Passes just fine, and matches expectations
    
    // Wait until the mock has been called once
    await waitFor(() => expect(mockSpy).toHaveBeenCalled(1)) // Also passes, meaning the mock was called

    // Has the page rendered once the loading mock has finished
    expect(result.asFragment()).toMatchSnapshot(); // Passes, but the page has rendered without any of the data
})

The problem is this: when I run this test, all three of those tests pass as expected, but in the final fragment the data in my rendered ponent is missing. I am sure the mock is being called because I've added some logger statements to check.

The really confusing part are the loading, data, and error values as the mock is called. I have a useEffect statement logging their values when any of them change, and when I run the test, the output looks like this:

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

This means that the hook is being called and loading begins, but once loading ends whatever happened during loading neither returned any data nor generated any errors.

Does anybody know what my problem here might be? I've looked at it eight ways to Sunday and can't figure it out.

Some background: I've got a ponent that immediately calls a useQuery hook upon loading. While that query is running, I spin a loading spinner. Once it pletes I render stuff based on the data.

I've added a useEffect hook that watches the result of the query and logs the data, which is how I observed this issue.

To simplify things, it works like this:

export default function MyComponent(props: ???) {
    const result = useQuery(INITIAL_DATA_QUERY, { variables: { id: 1 } });

    React.useEffect(() => console.log(JSON.stringify({
        loading: result.loading,
        data: result.data,
        error: result.error,
    })), [result]);

    if (result.loading) return <LoadingScreen message="Fetching data..."/>;
    else if (result.error) return <ErrorPage/>
    else return <Stuff info={result.data}> // omitted because it's unimportant to the issue
}

When I run this ponent in the wild, everything works exactly as expected. It hits the endpoint with GraphQL through Apollo, makes rendering decisions based on the result, etc.

When I try to mock the request out though, the result.data and result.error fields never change, even though the result.loading field does. I am using react-testing-library to run the tests.

My tests look like this:

it("should load the data then render the page", () => {

    const mocks = [{
        request: {
            query: INITIAL_DATA_QUERY,
            variables: { id: 1 },
        },
        newData: jest.fn(() => ({
            data: {
                firstName: "Joe",
                lastName: "Random",
            }
        }))
    }];

    const mockSpy = mocks[0].newData;

    render(
        <MockedProvider mocks={mocks} addTypename={false}>
            <MyComponent/>
        </MockedProvider>
    )

    // Is it a loading view
    expect(result.asFragment()).toMatchSnapshot(); // Passes just fine, and matches expectations
    
    // Wait until the mock has been called once
    await waitFor(() => expect(mockSpy).toHaveBeenCalled(1)) // Also passes, meaning the mock was called

    // Has the page rendered once the loading mock has finished
    expect(result.asFragment()).toMatchSnapshot(); // Passes, but the page has rendered without any of the data
})

The problem is this: when I run this test, all three of those tests pass as expected, but in the final fragment the data in my rendered ponent is missing. I am sure the mock is being called because I've added some logger statements to check.

The really confusing part are the loading, data, and error values as the mock is called. I have a useEffect statement logging their values when any of them change, and when I run the test, the output looks like this:

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

This means that the hook is being called and loading begins, but once loading ends whatever happened during loading neither returned any data nor generated any errors.

Does anybody know what my problem here might be? I've looked at it eight ways to Sunday and can't figure it out.

Share Improve this question edited Aug 10, 2021 at 20:18 IanCZane asked Aug 10, 2021 at 20:09 IanCZaneIanCZane 6406 silver badges23 bronze badges 2
  • 2 I figured this out. It was related to my specific data. The mock that I supplied to the MockedProvider was a different shape than the GraphQL query, so it ended up returning nothing. Lesson: make sure the data you're outputting from the mock matches the expected GraphQL shape. – IanCZane Commented May 20, 2022 at 17:13
  • Sigh, ended on the same lesson: Lesson: make sure the data you're outputting from the mock matches the expected GraphQL shape. – edmundo096 Commented Jun 6, 2023 at 0:04
Add a ment  | 

1 Answer 1

Reset to default 3

I mocked the result using the result field.

the result field can be a function that returns a mocked response after performing arbitrary logic

It works fine for me.

MyComponent.test.tsx:

import { gql, useQuery } from '@apollo/client';
import { useEffect } from 'react';

export const INITIAL_DATA_QUERY = gql`
  query GetUser($id: ID!) {
    user(id: $id) {
      firstName
      lastName
    }
  }
`;

export default function MyComponent(props) {
  const result = useQuery(INITIAL_DATA_QUERY, { variables: { id: 1 } });

  useEffect(
    () =>
      console.log(
        JSON.stringify({
          loading: result.loading,
          data: result.data,
          error: result.error,
        }),
      ),
    [result],
  );

  if (result.loading) return <p>Fetching data...</p>;
  else if (result.error) return <p>{result.error}</p>;
  else return <p>{result.data.user.firstName}</p>;
}

MyComponent.test.tsx:

import { render, waitFor } from '@testing-library/react';
import MyComponent, { INITIAL_DATA_QUERY } from './MyComponent';
import { MockedProvider } from '@apollo/client/testing';

describe('68732957', () => {
  it('should load the data then render the page', async () => {
    const mocks = [
      {
        request: {
          query: INITIAL_DATA_QUERY,
          variables: { id: 1 },
        },
        result: jest.fn().mockReturnValue({
          data: {
            user: {
              lastName: 'Random',
              firstName: 'Joe',
            },
          },
        }),
      },
    ];

    const mockSpy = mocks[0].result;
    const result = render(
      <MockedProvider mocks={mocks} addTypename={false}>
        <MyComponent />
      </MockedProvider>,
    );

    expect(result.asFragment()).toMatchSnapshot();
    await waitFor(() => expect(mockSpy).toBeCalledTimes(1));
    expect(result.asFragment()).toMatchSnapshot();
  });
});

test result:

 PASS  src/stackoverflow/68732957/MyComponent.test.tsx
  68732957
    ✓ should load the data then render the page (58 ms)

  console.log
    {"loading":true}

      at src/stackoverflow/68732957/MyComponent.tsx:18:15

  console.log
    {"loading":false,"data":{"user":{"firstName":"Joe","lastName":"Random"}}}

      at src/stackoverflow/68732957/MyComponent.tsx:18:15

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   2 passed, 2 total
Time:        0.736 s, estimated 1 s

MyComponent.test.tsx.snap:

// Jest Snapshot v1

exports[`68732957 should load the data then render the page 1`] = `
<DocumentFragment>
  <p>
    Fetching data...
  </p>
</DocumentFragment>
`;

exports[`68732957 should load the data then render the page 2`] = `
<DocumentFragment>
  <p>
    Joe
  </p>
</DocumentFragment>
`;

package versions:

"@testing-library/react": "^11.1.0",
"react": "^17.0.1",
"@apollo/client": "^3.4.7",
"graphql": "^15.4.0"

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论