Accessing unique queryClients in Storybook-based Jest tests
Tags: react, storybook, testing, javascript
Published: 2021-12-13 14:22:28

tl;dr

I recently came up with a pattern for accessing unique [React Query](https://react-query.tanstack.com/) clients on a per-render basis in [Storybook](https://storybook.js.org) stories and tests (using [Jest](https://jestjs.io/) and [React Testing Library](https://testing-library.com/docs/react-testing-library/intro/)). This enables the following kind of API:

```tsx
// composeStories from `@storybook/testing-react`
const Story = composeStories(stories)
const { queryClient } = renderStory(<Story.FancyButton />)
// ...wait for query client state, assert state, etc
```

(If this sounds like a fun stack to you, [we're hiring](https://www.process.st/jobs/) at [Process Street](https://www.process.st/)!)

Keep reading for more of the story, or just jump into the code in this template:

{% codesandbox 168r5 %}

---

> I prefer to **give each test its own QueryClientProvider** and create a **new QueryClient for each test**. That way, *tests are completely isolated* from each other. A different approach might be to clear the cache after each test, but I like to keep shared state between tests as minimal as possible. Otherwise, you might get **unexpected and flaky results if you run your tests in parallel**.

> \- TkDodo on [Testing React Query](https://tkdodo.eu/blog/testing-react-query)

*...But what if I need access to the query client in my tests!?*

At Process Street we use the lovely [@storybook/testing-react](https://github.com/storybookjs/testing-react) package to use Storybook stories as our components under test. This is a great pattern because you can effectively automate the manual QA you do in the Storybook browser.

Let's say you just added a toast alert for an API exception case. You finish your code changes and switch to your browser to test the toast in the story. It works! Now you can keep your user hat on and use [Testing Library](https://testing-library.com/) fundamentals to write a Jest spec matching what you did in the browser. 

An example (pseudo code):

```tsx
import * as React from 'react'
// local util module to wrap test utils like React Testing 
// Library (RTL) and @storybook/testing-react
import * as Test from 'test'
import * as stories from './index.stories'

const Story = Test.composeStories(stories)

test("FancyButton shows an alert for failures", () => {
  Test.renderStory(<Story.Failure />)
  Test.user.click(Test.screen.getByText(/do stuff/i))
  await Test.findByText(/uh oh!/i)
})
```

Yep. That's it. For the past few months our `*.spec.tsx` files have been _very_ concise and declarative. This is because all the setup is in `*.stories.tsx` files. Tests just become expressions of how I'm testing the stories, as a user, in the browser.

Along with testing "integrated" components powered by React Query, we've been using a global `queryClient` instance to leverage patterns like:

```tsx
await Test.waitFor(() => {
  expect(queryClient.isFetching()).toEq(0)
})
```

We can't follow TkDodo's advice very easily because if each story sets up it's own query client, how do we get access to it? We _could_ instantiate a new query client for each test, but that's the kind of boilerplate that makes testing feel terrible. **My goal is always to make testing feel as good as possible (maybe even fun?!).**

The code I _wish_ I had has the following characteristics:

1. All stories and tests have a unique `queryClient` instance.
2. The unique `queryClient` instance is easily accessible in each test.
3. The pattern for accessing the `queryClient` _feels_ like "The Testing Library Way".

By #3, I refer to how Testing Library has normalized our eyes to the pattern of rendering something and destructuring results from that render call.

```tsx
const { rerender } = Test.render(<FancyButton />)
```

It would feel awfully nice to be able to do something like:

```tsx
const { queryClient } = Test.render(<FancyButton />)
```

Right? It's quite clear that the `queryClient` is unique to this particular invocation of `Test.render`.

So the big question is, how do we implement this?

I know right away that I won't instantiate the `queryClient` nor the `QueryClientProvider` at the individual story level for the same reasons I wouldn't instantiate it in each test: too much annoying boilerplate that makes writing stories less fun. So that's out. We need some kind of "do this for every test" lever.

It's [recommended](https://testing-library.com/docs/react-testing-library/setup/#custom-render) to have a custom `render` function that wraps the component under test the same way your app is globally wrapped by some combination of providers. We'll borrow this notion of "all the providers" but skip the custom render wrapper and instead use it for a Storybook [decorator](https://storybook.js.org/docs/react/writing-stories/decorators). Since we'll want control of our `queryClient`, we'll parameterize it for a root provider.

```tsx
// ./test/index.tsx
import React from "react";
import { render, RenderOptions } from "@testing-library/react";
import { QueryClient, QueryClientProvider } from "react-query";
import { ChakraProvider } from "@chakra-ui/react";

export const makeQueryClient = () =>
  new QueryClient({
    defaultOptions: { queries: { retry: false } }
  });

type Props = { queryClient?: QueryClient };

export const AllTheProviders: React.FC<Props> = ({
  queryClient = makeQueryClient(),
  children
}) => {
  return (
    <QueryClientProvider client={queryClient}>
      <ChakraProvider>{children}</ChakraProvider>
    </QueryClientProvider>
  );
};
```

Now we'll jump straight to decorating all stories with `AllTheProviders`.

```tsx
// .storybook/main-decorator.tsx
import * as React from "react";
import { AllTheProviders } from "../test";

export const MainDecorator: DecoratorFn = (
  Story,
  options
) => {
  return (
    <AllTheProviders queryClient={options.args.queryClient}>
      <Story {...options} />
    </AllTheProviders>
  );
};
```
Note that `options.args.queryClient` is still nullable, but allows us to pass a query client to the component results of `composeStories`.

Now we just export that decorator for Storybook's browser configuration in `preview.js`.

```tsx
// .storybook/preview.js
import { MainDecorator } from './main-decorator'
//...
export const decorators = [AllTheProviders]
```

Now we have "decorated" stories for testing with `composeStories` from `@storybook/testing-react`, but we need a custom render function that adds `queryClient` to the return value of `render` from React Testing Library.

```tsx
export const renderStory = (
  ui: React.ReactElement<{ queryClient?: QueryClient }>,
  options: RenderOptions = {}
) => {
  const queryClient: QueryClient =
    ui.props.queryClient ?? makeQueryClient();
  const clonedUi = React.cloneElement(ui, { queryClient });
  return { ...render(clonedUi, options), queryClient };
};
```

Boom! We use `React.cloneElement` to modify the already-invoked component function so we can pass a `queryClient` from a different scope. If the `ui` component was already called with a `queryClient`, that will be reused thanks to our nullish coalescing operator `??`. Now in our tests we can access the `queryClient` as a _result_ of our `render` call.

```tsx
const { queryClient } = Test.renderStory(<Story.FancyButton />)
```

If you do need to test implementation details (which, face it, sometimes you Just Do), you can do something like this:

```tsx
const queryClient = makeQueryClient()
const invalidateQueriesSpy = 
  jest.spyOn(queryClient, 'invalidateQueries');

Test.render(<Story.Success queryClient={queryClient} />)
Test.user.click(Test.screen.getByText(/do stuff/i))
expect(queryClient.invalidateQueries)
  .toHaveBeenCalledWith("user-profile")
```

That's where the `ui.props.queryClient` check comes into play.

And that's it! Check out the [sandbox](https://codesandbox.io/s/react-query-storybook-based-testing-168r5) for more implementation details. Happy testing!