or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

client-access.mdindex.mdmutation-operations.mdquery-operations.mdsubscription-operations.mdtesting-utilities.md
tile.json

testing-utilities.mddocs/

Testing Utilities

Components and utilities for testing GraphQL operations in React applications.

Capabilities

MockProvider Component

Mock Apollo Client provider for testing GraphQL operations without making real network requests.

/**
 * Mock Apollo Client provider for testing GraphQL operations
 */
declare const MockProvider: React.ComponentType<MockProviderProps>;

interface MockProviderProps {
  /** Array of mocked GraphQL responses */
  mocks?: ReadonlyArray<MockedResponse>;
  /** Whether to add __typename to responses */
  addTypename?: boolean;
  /** Default options for Apollo Client */
  defaultOptions?: DefaultOptions;
  /** Custom cache instance */
  cache?: ApolloCache<any>;
  /** Schema resolvers */
  resolvers?: Resolvers;
  /** Additional props passed to children */
  childProps?: object;
  /** React children */
  children?: React.ReactElement;
  /** Whether to show warnings for unmatched queries */
  showWarnings?: boolean;
}

interface MockedResponse<TData = Record<string, any>, TVariables = Record<string, any>> {
  /** GraphQL query/mutation/subscription to mock */
  request: MockedRequest<TVariables>;
  /** Mocked response data */
  result?: MockedResult<TData>;
  /** Error to return instead of result */
  error?: Error;
  /** Delay before returning response (ms) */
  delay?: number;
  /** How many times this mock can be used */
  maxUsageCount?: number;
}

interface MockedRequest<TVariables = Record<string, any>> {
  /** GraphQL query document */
  query: DocumentNode;
  /** Variables for the query */
  variables?: TVariables;
  /** Operation name */
  operationName?: string;
  /** Context for the request */
  context?: Record<string, any>;
}

interface MockedResult<TData = Record<string, any>> {
  /** Response data */
  data?: TData;
  /** GraphQL errors */
  errors?: ReadonlyArray<GraphQLError>;
  /** Extensions */
  extensions?: any;
}

Usage Examples:

import { MockProvider, gql } from "@apollo/react-hooks";
import { render, screen, waitFor } from "@testing-library/react";

const GET_USERS = gql`
  query GetUsers {
    users {
      id
      name
      email
    }
  }
`;

const CREATE_USER = gql`
  mutation CreateUser($input: CreateUserInput!) {
    createUser(input: $input) {
      id
      name
      email
    }
  }
`;

// Basic mocking
const mocks = [
  {
    request: {
      query: GET_USERS,
    },
    result: {
      data: {
        users: [
          { id: "1", name: "John Doe", email: "john@example.com" },
          { id: "2", name: "Jane Smith", email: "jane@example.com" },
        ],
      },
    },
  },
  {
    request: {
      query: CREATE_USER,
      variables: {
        input: { name: "New User", email: "new@example.com" },
      },
    },
    result: {
      data: {
        createUser: {
          id: "3",
          name: "New User", 
          email: "new@example.com",
        },
      },
    },
  },
];

function UsersList() {
  const { loading, error, data } = useQuery(GET_USERS);
  
  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  
  return (
    <ul>
      {data.users.map(user => (
        <li key={user.id}>{user.name} - {user.email}</li>
      ))}
    </ul>
  );
}

test("renders user list", async () => {
  render(
    <MockProvider mocks={mocks} addTypename={false}>
      <UsersList />
    </MockProvider>
  );

  expect(screen.getByText("Loading...")).toBeInTheDocument();
  
  await waitFor(() => {
    expect(screen.getByText("John Doe - john@example.com")).toBeInTheDocument();
    expect(screen.getByText("Jane Smith - jane@example.com")).toBeInTheDocument();
  });
});

Error Mocking:

import { MockProvider, gql, useQuery } from "@apollo/react-hooks";

const GET_USER = gql`
  query GetUser($id: ID!) {
    user(id: $id) {
      id
      name
      email
    }
  }
`;

const errorMocks = [
  {
    request: {
      query: GET_USER,
      variables: { id: "1" },
    },
    error: new Error("User not found"),
  },
  {
    request: {
      query: GET_USER,
      variables: { id: "2" },
    },
    result: {
      errors: [{ message: "Access denied" }],
    },
  },
];

function UserProfile({ userId }: { userId: string }) {
  const { loading, error, data } = useQuery(GET_USER, {
    variables: { id: userId },
  });
  
  if (loading) return <div>Loading user...</div>;
  if (error) return <div>Error: {error.message}</div>;
  
  return (
    <div>
      <h2>{data.user.name}</h2>
      <p>{data.user.email}</p>
    </div>
  );
}

test("handles query errors", async () => {
  render(
    <MockProvider mocks={errorMocks}>
      <UserProfile userId="1" />
    </MockProvider>
  );

  await waitFor(() => {
    expect(screen.getByText("Error: User not found")).toBeInTheDocument();
  });
});

Advanced Mocking with Dynamic Responses:

import { MockProvider, gql } from "@apollo/react-hooks";

const SEARCH_USERS = gql`
  query SearchUsers($query: String!) {
    searchUsers(query: $query) {
      id
      name
      email
    }
  }
`;

// Mock with multiple usage count
const searchMocks = [
  {
    request: {
      query: SEARCH_USERS,
      variables: { query: "john" },
    },
    result: {
      data: {
        searchUsers: [
          { id: "1", name: "John Doe", email: "john@example.com" },
        ],
      },
    },
    maxUsageCount: 3, // Can be used 3 times
  },
  {
    request: {
      query: SEARCH_USERS,
      variables: { query: "jane" },
    },
    result: {
      data: {
        searchUsers: [
          { id: "2", name: "Jane Smith", email: "jane@example.com" },
        ],
      },
    },
    delay: 1000, // 1 second delay
  },
];

// Mock with custom resolvers
const mockResolvers = {
  Query: {
    users: () => [
      { id: "1", name: "Mock User 1", email: "mock1@example.com" },
      { id: "2", name: "Mock User 2", email: "mock2@example.com" },
    ],
  },
  Mutation: {
    createUser: (_, { input }) => ({
      id: Math.random().toString(),
      ...input,
    }),
  },
};

test("uses custom resolvers", async () => {
  render(
    <MockProvider resolvers={mockResolvers}>
      <UsersList />
    </MockProvider>
  );

  await waitFor(() => {
    expect(screen.getByText("Mock User 1 - mock1@example.com")).toBeInTheDocument();
  });
});

Types

interface DefaultOptions {
  /** Default options for queries */
  query?: Partial<QueryOptions>;
  /** Default options for mutations */  
  mutate?: Partial<MutationOptions>;
  /** Default options for watch queries */
  watchQuery?: Partial<WatchQueryOptions>;
}

interface Resolvers {
  [typeName: string]: {
    [fieldName: string]: FieldResolver<any, any, any>;
  };
}

type FieldResolver<TSource, TContext, TArgs> = (
  source: TSource,
  args: TArgs,
  context: TContext,
  info: GraphQLResolveInfo
) => any;

interface GraphQLError {
  /** Error message */
  message: string;
  /** Error locations in query */
  locations?: ReadonlyArray<SourceLocation>;
  /** Error path */
  path?: ReadonlyArray<string | number>;
  /** Additional error data */
  extensions?: Record<string, any>;
}

interface SourceLocation {
  line: number;
  column: number;
}