跳到主要内容

为 Storybook 模拟数据

Storybook 是一个用于隔离开发和测试的优秀工具,有望大幅缩短开发时间。

<MockResolver /> 可以轻松加载 fixture 或 interceptor,以查看不同的网络响应会呈现什么样子。它可以分层、组合,甚至可以用于命令式获取,这通常用于带副作用的 endpoint,例如 getList.push 和 update。

设置​

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

static key = 'Article';
}
export const ArticleResource = resource({
urlPrefix: 'http://test.com',
path: '/article/:id',
schema: Article,
searchParams: {} as { maxResults: number },
});

export let ArticleFixtures: Record<string, Fixture[]> = {};

Fixtures​

我们将用 fixture 和 interceptor 测试三种情况:列表中有一些有意义的结果、列表为空,以及数据不存在从而显示加载 fallback。

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,
response: ({ id }, body) => ({
...body,
id,
}),
},
],
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: [],
};
}

装饰器​

你需要添加合适的全局装饰器来建立正确的 context。

这应当与你在初始设置中添加的内容类似

.storybook/preview.tsx
import { Suspense } from 'react';
import { DataProvider, AsyncBoundary } from '@data-client/react';

export const decorators = [
Story => (
<DataProvider>
<AsyncBoundary>
<Story />
</AsyncBoundary>
</DataProvider>
),
];

Story​

用 <MockResolver /> 包裹组件,我们就可以声明式地控制 Reactive Data Client 的请求如何被解析。

这里我们通过 storybook controls 选择要使用的 fixture。

ArticleList.stories.tsx
import { type StoryObj } from '@storybook/react';
import { MockResolver } from '@data-client/test';
import type { Fixture } from '@data-client/test';

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),
},
},
},
};

export const FullArticleList: StoryObj<{ result: keyof typeof ArticleFixtures }> =
{
render: ({ result }) => (
<MockResolver fixtures={ArticleFixtures[result]}>
<ArticleList maxResults={10} />
</MockResolver>
),
args: { result: 'full' },
};