diff --git a/.storybook/stories/Welcome.mdx b/.storybook/stories/Welcome.mdx index 14538aa..0837e9e 100644 --- a/.storybook/stories/Welcome.mdx +++ b/.storybook/stories/Welcome.mdx @@ -30,7 +30,6 @@ This library has `peerDependencies` listings for `msw` at `^2.0.0` and `graphql` npm install --save-dev @apollo/graphql-testing-library msw graphql pnpm add --save-dev @apollo/graphql-testing-library msw graphql yarn add --dev @apollo/graphql-testing-library msw graphql -bun add --dev @apollo/graphql-testing-library msw graphql ``` ## Usage diff --git a/README.md b/README.md index da643ba..be7caf4 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,6 @@ This library has `peerDependencies` listings for `msw` at `^2.0.0` and `graphql` npm install --save-dev @apollo/graphql-testing-library msw graphql pnpm add --save-dev @apollo/graphql-testing-library msw graphql yarn add --dev @apollo/graphql-testing-library msw graphql -bun add --dev @apollo/graphql-testing-library msw graphql ``` ## Usage diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index 520c2d0..9c6ac49 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -21,7 +21,10 @@ export default defineConfig({ }, { label: "Guides", - items: [{ label: "Creating a handler", slug: "creating-a-handler" }], + items: [ + { label: "Creating a handler", slug: "creating-a-handler" }, + { label: "Writing a test", slug: "writing-a-test" }, + ], }, { label: "Storybook Examples", diff --git a/docs/src/content/docs/creating-a-handler.mdx b/docs/src/content/docs/creating-a-handler.mdx index cbfb80b..f07b787 100644 --- a/docs/src/content/docs/creating-a-handler.mdx +++ b/docs/src/content/docs/creating-a-handler.mdx @@ -2,3 +2,71 @@ title: Creating a handler # description: A guide in my new Starlight docs site. --- + +import { Aside } from "@astrojs/starlight/components"; + +Once [Mock Service Worker](https://mswjs.io/) has been configured in your environment, you're ready to create your first request handler and begin writing mock resolvers. + +Create a `handlers.ts` inside the `mocks` folder you created during MSW set-up. + +## `createHandler` + +Let's create a GraphQL request handler using the `createHandler` function. + +<Aside type="tip"> + +If your project is using [GraphQL-Codegen](https://the-guild.dev/graphql/codegen) or a similar tool to generate resolver types, you can pass `Resolvers` as a generic type argument to `createHandler` to type check mock resolvers. + +</Aside> + +```ts +// src/mocks/handlers.ts +import { createHandler } from "@apollo/graphql-testing-library"; +import typeDefs from "../../schema.graphql"; +import type { Resolvers } from "../../__generated__/resolvers-types.ts"; + +const graphQLHandler = createHandler<Resolvers>({ + typeDefs, + // Configure the mock resolvers for your handler. + // `resolvers` can be overridden in individual tests. + resolvers: { + Query: { + products: (parent, arguments, context, info) => + Array.from({ length: 2 }, (element, index) => ({ + id: `${index}`, + title: `Product ${index}`, + })), + }, + }, + // Mock any type in the schema directly. + // `mocks` can also be overridden in individual tests. + mocks: { + FooBarType: () => ({ + foo: "bar", + baz: "qux", + }), + CustomScalar: () => "Foo bar", + String: () => "Hello world", + }, +}); +``` + +The handler `graphQLHandler` can now be passed to Mock Service Worker's [`setupServer` in Node.js](https://mswjs.io/docs/integrations/node#setup) or [`setupWorker` in the browser](). It will intercept all GraphQL operations and generate a response using your mock schema. 🚀 + +## Default mocks + +Using the provided schema's type definitions, GraphQL Testing Library creates a mock schema under the hood with a set of default mocks: + +- **Union and Interface types**: Union and Interface types are configured with a default `__resolveType` function set that selects the first possible type (can be overriden via mock `resolvers`). +- **Enum types**: Enum types return the first possible value by default (can be overridden via `mocks` option). +- **Custom scalars**: Custom scalars return `Default value for custom scalar ${typeName}` by default (can be overridden via `mocks` option). + +<Aside type="note"> + +For full control over the creation of your mock schema, use the `createHandlerFromSchema` API instead which takes a `GraphQLSchema` instead of a `DocumentNode`. + +</Aside> + +## Request timing + +- talk about `delay` diff --git a/docs/src/content/docs/index.mdx b/docs/src/content/docs/index.mdx index 14dee9c..37494e2 100644 --- a/docs/src/content/docs/index.mdx +++ b/docs/src/content/docs/index.mdx @@ -19,7 +19,7 @@ hero: import { Card, CardGrid } from "@astrojs/starlight/components"; <CardGrid stagger> - <Card title="Client and framework-agnostic" icon="puzzle"> + <Card title="Client- and framework-agnostic" icon="puzzle"> GraphQL Testing Library can be used to test apps built with any GraphQL client or front-end stack. </Card> @@ -30,7 +30,7 @@ import { Card, CardGrid } from "@astrojs/starlight/components"; <Card title="Schema-driven testing" icon="seti:graphql" color="pink"> Goodbye hand-written response mocks, hello mock resolvers. </Card> - <Card title="With the power of MSW" icon="rocket"> + <Card title="Powered by MSW" icon="rocket"> Learn more about Mock Service Worker by exploring [the MSW docs](https://mswjs.io/). </Card> diff --git a/docs/src/content/docs/installation.mdx b/docs/src/content/docs/installation.mdx index 33b2cdd..00bee18 100644 --- a/docs/src/content/docs/installation.mdx +++ b/docs/src/content/docs/installation.mdx @@ -42,12 +42,5 @@ pnpm add --save-dev @apollo/graphql-testing-library msw graphql yarn add --dev @apollo/graphql-testing-library msw graphql ``` -</TabItem> -<TabItem label="Bun"> - -```sh -bun add --dev @apollo/graphql-testing-library msw graphql -``` - </TabItem> </Tabs> diff --git a/docs/src/content/docs/integrations/browser.mdx b/docs/src/content/docs/integrations/browser.mdx index e30515b..0901480 100644 --- a/docs/src/content/docs/integrations/browser.mdx +++ b/docs/src/content/docs/integrations/browser.mdx @@ -3,13 +3,15 @@ title: Usage in the browser # description: A guide in my new Starlight docs site. --- -import { Tabs, TabItem } from "@astrojs/starlight/components"; +import { Tabs, TabItem, Aside } from "@astrojs/starlight/components"; ## With Storybook [Mock Service Worker](https://mswjs.io/) and [GraphQL Testing Library](https://github.com/apollographql/graphql-testing-library) pair nicely with [Storybook](https://storybook.js.org/) when developing in the browser. -To get started, install the [Mock Service Worker Storybook addon](https://storybook.js.org/addons/msw-storybook-addon). +### Installation + +To get started, install the [Mock Service Worker Storybook addon](https://storybook.js.org/addons/msw-storybook-addon) and configure it using the instructions in the README. <Tabs syncKey="pkg"> <TabItem label="npm"> @@ -33,11 +35,100 @@ yarn add --dev msw-storybook-addon ``` </TabItem> -<TabItem label="Bun"> +</Tabs> -```sh -bun add --dev msw-storybook-addon +### Usage + +```ts +// storybook/preview.tsx +import { initialize, mswLoader } from "msw-storybook-addon"; +import { HttpResponse, http } from "msw"; +import { createHandler } from "@apollo/graphql-testing-library"; +import typeDefs from "../../../.storybook/stories/ecommerce-schema.graphql"; + +// Initialize MSW +initialize(); + +// Provide the MSW addon loader globally +export const loaders = [mswLoader]; + +const defaultHandlers = { + // Initialize default handlers here, including any non-GraphQL endpoints. + // For example, let's say this app displays the status of a service by + // making a GET request to a dedicated endpoint. + apiStatus: http.get("https://status.example.com/api/status.json", () => { + return HttpResponse.json({ + status: { description: "All Systems Operational!" }, + }); + }), + // Default handler for all GraphQL requests. + // Individual stories will overwrite this handler via the + // exported ComponentName.parameters.msw.handlers.graphql property. + graphql: createHandler({ + typeDefs, + resolvers: { + Query: { + // Your mock resolvers here + }, + }, + }), +}; + +export const parameters = { + msw: { + handlers: defaultHandlers, + }, +}; ``` -</TabItem> -</Tabs> +If your app is using a GraphQL client that relies on the presence of a provider component, like Apollo Client with `ApolloProvider`, you can provide it globally via decorator. + +<Aside type="note"> + +In general, be wary of sharing cache instances across stories or tests. On the Apollo Client team, we advise you not to share a single `ApolloClient` instance because, even if the cache is reset, the client maintains some internal state that is not reset which could have unintended consequences. + +By creating a `makeClient` function or equivalent, every test or story can use the same client configuration as your production client, but no two tests or stories share the same instance. + +</Aside> + +```tsx +// storybook/preview.tsx +import { ApolloProvider } from "@apollo/client"; +import { Decorator } from "@storybook/react"; + +export const makeClient = () => { + return new ApolloClient({ + cache: new InMemoryCache(), + uri: "https://example.com/graphql", + }); +}; + +export const decorators: Decorator[] = [ + (story) => <ApolloProvider client={makeClient()}>{story()}</ApolloProvider>, +]; +``` + +Now, in individual Storybook stories, the `graphql` handler can be overridden by setting a new handler via `parameters`: + +```tsx +// storybook/MyComponent.story.tsx +import { Meta, StoryObj } from "@storybook/react"; +import { createHandler } from "@apollo/graphql-testing-library"; +import { MyComponent } from "./MyComponent"; + +// You do not need to specify a default GraphQL handler, +// since it's already been set in preview.tsx +export default { + component: MyComponent, +} as Meta<typeof MyComponent>; + +export const ErrorState: StoryObj<typeof MyComponent> = { + parameters: { + msw: { + handlers: { + graphql: createHandler({}), + }, + }, + }, +}; +``` diff --git a/docs/src/content/docs/integrations/node.mdx b/docs/src/content/docs/integrations/node.mdx index 9e24dcb..1fd4015 100644 --- a/docs/src/content/docs/integrations/node.mdx +++ b/docs/src/content/docs/integrations/node.mdx @@ -13,6 +13,12 @@ In order to use GraphQL Testing Library and MSW with Jest, missing Node.js globa Create a `jest.polyfills.js` file with the following contents: +<Aside type="note"> + +Be sure to also install `undici`, the official fetch implementation in Node.js. + +</Aside> + ```ts // jest.polyfills.js /** @@ -62,13 +68,7 @@ if (!Symbol.asyncDispose) { } ``` -This file mostly contains the [Jest polyfills recommended by MSW](https://mswjs.io/docs/faq/#requestresponsetextencoder-is-not-defined-jest), with two additions: the version above includes `ReadableStream` which is needed for incremental delivery features, and a polyfill for `Symbol.dispose` which is not available in Jest in versions before `30.0.0-alpha.3`. - -<Aside type="note"> - -Be sure to also install `undici`, the official fetch implementation in Node.js. - -</Aside> +This file mostly contains the [Jest polyfills recommended by MSW](https://mswjs.io/docs/faq/#requestresponsetextencoder-is-not-defined-jest), with two additions: the version above includes `ReadableStream` which is needed for incremental delivery features, and a polyfill for `Symbol.dispose` which is not available in Jest in versions below `30.0.0-alpha.3`. Then, set the `setupFiles` option in `jest.config.js` to point to your `jest.polyfills.js`: @@ -81,7 +81,7 @@ module.exports = { Next, follow the Mock Service Worker documentation for [setting up MSW in Node.js](https://mswjs.io/docs/integrations/node). -Finally, install `@graphql-tools/jest-transform` as a dev dependency and configure Jest to transform `.gql`/`.graphql` files, since your GraphQL API's schema is needed to configure the Mock Service Worker [request handler](https://mswjs.io/docs/concepts/request-handler/) this library generates. +Finally, install `@graphql-tools/jest-transform` as a dev dependency and configure Jest to transform `.gql`/`.graphql` files, since your GraphQL API's schema document is required when [creating a request handler](/creating-a-handler). Here are the relevant parts of your final `jest.config.js`: @@ -105,11 +105,13 @@ module.exports = { ## With Vitest -No polyfills are needed in Vitest. In order to transform `.gql`/`.graphql` files, install `vite-plugin-graphql-loader` as a dev dependency and configure it in your `vitest.config.ts`. +Vitest does not need to be configured with any polyfills. -<Aside type="note"> +In order to transform `.gql`/`.graphql` files, install `vite-plugin-graphql-loader` as a dev dependency and configure it in your `vitest.config.ts`. + +<Aside type="caution"> -In order to avoid the `graphql` error `Ensure that there is only one instance of "graphql" in the node_modules directory` caused by the [dual package hazard](https://nodejs.org/api/packages.html#dual-package-hazard), set [`server.deps.fallbackCJS`](https://vitest.dev/config/#server-deps-fallbackcjs) to `true`. +In order to avoid the `graphql` error `Ensure that there is only one instance of "graphql" in the node_modules directory` caused by the [dual package hazard](https://nodejs.org/api/packages.html#dual-package-hazard), please set [`server.deps.fallbackCJS`](https://vitest.dev/config/#server-deps-fallbackcjs) to `true`. </Aside> diff --git a/docs/src/content/docs/writing-a-test.mdx b/docs/src/content/docs/writing-a-test.mdx new file mode 100644 index 0000000..c51c07e --- /dev/null +++ b/docs/src/content/docs/writing-a-test.mdx @@ -0,0 +1,4 @@ +--- +title: Writing a test +# description: A guide in my new Starlight docs site. +---