Skip to main content
Version: 6.5

Mocking data for Storybook

Storybook is a great utility to do isolated development and testing, potentially speeding up development time greatly.

<MockResolver /> enables easy loading of fixtures to see what different network responses might look like. It can be layered, composed, and even used for imperative fetches usually used with side-effect endpoints like create and update.

Setup

ArticleResource.ts
export class Article extends Entity {
readonly id: number | undefined = undefined;
readonly content: string = '';
readonly author: number | null = null;
readonly contributors: number[] = [];

pk() {
return this.id?.toString();
}
}
export const ArticleResource = createResource({
urlPrefix: 'http://test.com',
path: '/article/:id',
schema: Article,
});

export let ArticleFixtures: Record<string, FixtureEndpoint> = {};

Fixtures

We'll test three cases: some interesting results in the list, an empty list, and data not existing so loading fallback is shown.

ArticleResource.ts
// leave out in production so we don't bloat the bundle
if (process.env.NODE_ENV !== 'production') {
ArticleFixtures = {
full: [
{
endpoint: ArticleResource.getList,
args: [{ maxResults: 10 }] as const,
response: [
{
id: 5,
content: 'have a merry christmas',
author: 2,
contributors: [],
},
{
id: 532,
content: 'never again',
author: 23,
contributors: [5],
},
],
},
{
endpoint: ArticleResource.update,
args: [{ id: 532 }] as const,
response: {
id: 532,
content: 'updated "never again"',
author: 23,
contributors: [5],
},
},
],
empty: [
{
endpoint: ArticleResource.getList,
args: [{ maxResults: 10 }] as const,
response: [],
},
],
error: [
{
endpoint: ArticleResource.getList,
args: [{ maxResults: 10 }] as const,
response: { message: 'Bad request', status: 400, name: 'Not Found' },
error: true,
},
],
loading: [],
};
}

Decorators

You'll need to add the appropriate global decorators to establish the correct context.

This should resemble what you have added in initial setup

.storybook/preview.tsx
import { Suspense } from 'react';
import { CacheProvider, NetworkErrorBoundary } from 'rest-hooks';

export const decorators = [
Story => (
<CacheProvider>
<Suspense fallback="loading">
<NetworkErrorBoundary>
<Story />
</NetworkErrorBoundary>
</Suspense>
</CacheProvider>
),
];

Story

Wrapping our component with <MockResolver /> enables us to declaratively control how Rest Hooks' fetches are resolved.

Here we select which fixtures should be used by storybook controls.

ArticleList.stories.tsx
import { MockResolver } from '@rest-hooks/test';
import type { Fixture } from '@rest-hooks/test';
import { Story } from '@storybook/react/types-6-0';

import ArticleList from 'ArticleList';
import { ArticleFixtures } from 'resources/ArticleResource';

export default {
title: 'Pages/ArticleList',
component: ArticleList,
argTypes: {
result: {
description: 'Results',
defaultValue: 'full',
control: {
type: 'select',
options: Object.keys(ArticleFixtures),
},
},
},
};

const Template: Story<{ result: keyof typeof options }> = ({ result }) => (
<MockResolver fixtures={options[result]}>
<ArticleList maxResults={10} />
</MockResolver>
);

export const FullArticleList = Template.bind({});

FullArticleList.args = {
result: 'full',
};