0

I'm trying to use graphql-tools's addMocksToSchema function in a jest test with Apollo Client to allow me to test components that query Apollo by returning mock data that I can define for myself. However, the mock data isn't being properly returned when using useQuery.

I have a UserButton button that I'm trying to write tests for. The component is as follows:

// UserButton.jsx
import React from "react";
import PropTypes from "prop-types";
import { gql } from "@apollo/client";
import { useQuery } from "@apollo/client";

import Button from "./Button";

const GET_CURRENT_USER = gql`
  query GetCurrentUser {
    id
    firstName
    lastName
  }
`;

function UserButton({ ...props }) {
  const { data: userData } = useQuery(GET_CURRENT_USER);
  return <Button {...props}>{userData.firstName} {userData.lastName}'s Button</Button>;
}

UserButton.propTypes = {
  children: PropTypes.node,
};

export default UserButton;

Here is my test:

// UserButton.test.jsx
import React from "react";
import { render, screen } from "@testing-library/react";
import UserButton from "../UserButton";
import ApolloMockingProvider from "../../providers/ApolloMockingProvider";
import userEvent from "@testing-library/user-event";
import { debug } from "webpack";

describe("UserButton", () => {
  let onClickMock;

  beforeEach(() => {
    jest.resetAllMocks();
    onClickMock = jest.fn();
  });

  it("Renders with first and last name.", () => {
    render(
      <ApolloMockingProvider>
        <UserButton />
      </ApolloMockingProvider>
    );

    const button = screen.getByRole("button");

    expect(button).toHaveTextContent("John Smith's Button")
  });
});

The ApolloMockingProvider is based off the following article and the code for it is as follows:

// ApolloMoockingProvider.jsx
import {
  ApolloClient,
  ApolloProvider,
  InMemoryCache,
} from "@apollo/client";
import { SchemaLink } from "@apollo/client/link/schema";
import { addMocksToSchema } from "@graphql-tools/mock";
import { makeExecutableSchema } from "@graphql-tools/schema";
import React from "react";

const schemaString = `
  type AvatarObjectType {
    id: Int,
    avatarUrl: String
  }
  scalar DateTime
  scalar GenericScalar
  type UserObjectType {
    id: Int,
    username: String,
    firstName: String,
    lastName: String,
    avatar: AvatarObjectType,
    dateCreated: DateTime,
    notificationPreferences: GenericScalar,
  }
  type Query {
    GetCurrentUser: UserObjectType,
  }
`;

const globalMocks = {
  String: () => "abc",
  Int: () => 56,
  UserObjectType: () => ({
    firstName: "John",
    lastName: "Smith",
  }),
};

const ApolloMockingProvider = ({ children }) => {
  const executableSchema = makeExecutableSchema({ typeDefs: schemaString });

  const schemaWithMocks = addMocksToSchema({
    schema: executableSchema,
    mocks: globalMocks,
  });

  const client = new ApolloClient({
    link: new SchemaLink({ schemaWithMocks }),
    cache: new InMemoryCache(),
  });

  return <ApolloProvider client={client}>{children}</ApolloProvider>;
};

export default ApolloMockingProvider;

The ApolloMockingProvider's globalMock's object that's passed into schemaWithMocks should return 'John Smith's Button' for the UserButton's Renders with first and last name. test. However, instead I get undefined for userData as shown by this error message:

 UserButton › Renders with first and last name.

    TypeError: Cannot read property 'firstName' of undefined

      16 | function UserButton({ ...props }) {
      17 |   const { data: userData } = useQuery(GET_CURRENT_USER);
    > 18 |   return <Button {...props}>{userData.firstName} {userData.lastName}'s Button</Button>;
         |                                       ^
      19 | }
      20 | 
      21 | UserButton.propTypes = {

      at UserButton (js/components/button/UserButton.jsx:18:39)

Anybody know what the error here is? Reproduction repo is here with the failing test being located here:

EDIT:

I added loading to the useQuery in UserButton but still get the same undefined error.

// UserButton.test.jsx
function UserButton({ ...props }) {
  const { data: userData, loading } = useQuery(GET_CURRENT_USER);
  let buttonText = "Loading...";

  if (!loading) {
    buttonText = `${userData.firstName} ${userData.lastName}'s Button`;
  }
  return <Button {...props}>{buttonText}</Button>;
}

And changed the UserButton.test.jsx test accordingly:

it("Renders with first and last name.", async () => {
    render(
      <ApolloMockingProvider>
        <UserButton />
      </ApolloMockingProvider>
    );

    const button = screen.getByRole("button");

    expect(button).toHaveTextContent("Loading...")

    await waitFor(() => {
      expect(screen.queryByText("John Smith's Button")).toBeInTheDocument();
    });
  });

But the error I get is still with userData being undefined

UserButton › Renders with first and last name.

    TypeError: Cannot read property 'firstName' of undefined

      19 | 
      20 |   if (!loading) {
    > 21 |     buttonText = `${userData.firstName} ${userData.lastName}'s Button`;
         |                              ^
      22 |   }
      23 |   return <Button {...props}>{buttonText}</Button>;
      24 | }
Fredmental
  • 445
  • 1
  • 7
  • 14
  • @xadm I added `loading` to the `useQuery` instance in the example(Check the EDIT) but it unfortunately didn't fix my issue. – Fredmental Apr 09 '21 at 13:46
  • test `if(userData)` ? fails? ... then not component fault ... sth bad in testing method/env ? ... why not to follow https://www.apollographql.com/docs/react/development-testing/testing/ ? – xadm Apr 09 '21 at 14:06
  • where is query resolver? mocking data only is not enough – xadm Apr 09 '21 at 14:15
  • `query resolver` is done using the `globalMocks` variable in the `ApolloMockingProvider`. And the article I linked in the question(https://stackoverflow.com/questions/67010343/using-graphql-toolss-addmockstoschema-in-a-jest-test-doesnt-work-with-gql-from) denotes the advantages of using this method over using Apollo's built in `MockedProvider` since mocked data will be returned automatically rather than having to define the result each time. – Fredmental Apr 09 '21 at 15:40
  • where is GetCurrentUser resolver? – xadm Apr 09 '21 at 15:43
  • `const globalMocks = { String: () => "abc", Int: () => 56, UserObjectType: () => ({ firstName: "John", lastName: "Smith", }), };` `UserObjectType` in `globalMock` will resolve firstName to `John` and lastName to `Smith` respectfully. – Fredmental Apr 09 '21 at 16:21
  • type resolvers vs [missing] query resolver ... what tells query to return one or another user object? – xadm Apr 09 '21 at 16:28

0 Answers0