模拟网络请求
对于发出网络请求的组件(例如,从 REST 或 GraphQL API 获取数据),您可以使用像 Mock Service Worker (MSW) 这样的工具来模拟这些请求。MSW 是一个 API 模拟库,它依赖于 Service Worker 来捕获网络请求,并提供模拟数据作为响应。
该 MSW 插件 将此功能引入 Storybook,允许您在故事中模拟 API 请求。以下是有关如何设置和使用该插件的概述。
设置 MSW 插件
首先,如果需要,运行以下命令来安装 MSW 和 MSW 插件
npm install msw msw-storybook-addon --save-dev
如果您尚未使用 MSW,请生成 MSW 工作所需的 Service Worker 文件
npx msw init public/
然后确保 Storybook 配置中的 staticDirs
属性包含生成的 Service Worker 文件(默认情况下位于 /public
中)
// Replace your-framework with the framework you are using (e.g., react-webpack5, vue3-vite)
import type { StorybookConfig } from '@storybook/your-framework';
const config: StorybookConfig = {
framework: '@storybook/your-framework',
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
staticDirs: ['../public', '../static'],
};
export default config;
最后,使用 项目级加载器 初始化插件并将其应用于所有故事
// Replace your-renderer with the renderer you are using (e.g., react, vue, etc.)
import { Preview } from '@storybook/your-renderer';
import { initialize, mswLoader } from 'msw-storybook-addon';
/*
* Initializes MSW
* See https://github.com/mswjs/msw-storybook-addon#configuring-msw
* to learn how to customize it
*/
initialize();
const preview: Preview = {
// ... rest of preview configuration
loaders: [mswLoader], // 👈 Add the MSW loader to all stories
};
export default preview;
模拟 REST 请求
如果您的组件从 REST API 获取数据,则可以使用 MSW 在 Storybook 中模拟这些请求。例如,考虑此文档屏幕组件
import React, { useState, useEffect } from 'react';
import { PageLayout } from './PageLayout';
import { DocumentHeader } from './DocumentHeader';
import { DocumentList } from './DocumentList';
// Example hook to retrieve data from an external endpoint
function useFetchData() {
const [status, setStatus] = useState<string>('idle');
const [data, setData] = useState<any[]>([]);
useEffect(() => {
setStatus('loading');
fetch('https://your-restful-endpoint')
.then((res) => {
if (!res.ok) {
throw new Error(res.statusText);
}
return res;
})
.then((res) => res.json())
.then((data) => {
setStatus('success');
setData(data);
})
.catch(() => {
setStatus('error');
});
}, []);
return {
status,
data,
};
}
export function DocumentScreen() {
const { status, data } = useFetchData();
const { user, document, subdocuments } = data;
if (status === 'loading') {
return <p>Loading...</p>;
}
if (status === 'error') {
return <p>There was an error fetching the data!</p>;
}
return (
<PageLayout user={user}>
<DocumentHeader document={document} />
<DocumentList documents={subdocuments} />
</PageLayout>
);
}
使用 MSW 插件,我们可以编写使用 MSW 模拟 REST 请求的故事。以下是如何为文档屏幕组件编写两个故事的示例:一个成功获取数据,另一个失败。
// Replace your-framework with the name of your framework (e.g. nextjs, vue3-vite)
import type { Meta, StoryObj } from '@storybook/your-framework';
import { http, HttpResponse, delay } from 'msw';
import { MyComponent } from './MyComponent';
const meta: Meta<typeof MyComponent> = {
component: DocumentScreen,
};
export default meta;
type Story = StoryObj<typeof MyComponent>;
// 👇 The mocked data that will be used in the story
const TestData = {
user: {
userID: 1,
name: 'Someone',
},
document: {
id: 1,
userID: 1,
title: 'Something',
brief: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
status: 'approved',
},
subdocuments: [
{
id: 1,
userID: 1,
title: 'Something',
content:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
status: 'approved',
},
],
};
export const MockedSuccess: Story = {
parameters: {
msw: {
handlers: [
http.get('https://your-restful-endpoint/', () => {
return HttpResponse.json(TestData);
}),
],
},
},
};
export const MockedError: Story = {
parameters: {
msw: {
handlers: [
http.get('https://your-restful-endpoint', async () => {
await delay(800);
return new HttpResponse(null, {
status: 403,
});
}),
],
},
},
};
模拟 GraphQL 请求
GraphQL 是另一种在组件中获取数据的常用方式。您可以使用 MSW 在 Storybook 中模拟 GraphQL 请求。以下是如何模拟从 GraphQL API 获取数据的文档屏幕组件示例
import { useQuery, gql } from '@apollo/client';
import { PageLayout } from './PageLayout';
import { DocumentHeader } from './DocumentHeader';
import { DocumentList } from './DocumentList';
const AllInfoQuery = gql`
query AllInfo {
user {
userID
name
}
document {
id
userID
title
brief
status
}
subdocuments {
id
userID
title
content
status
}
}
`;
interface Data {
allInfo: {
user: {
userID: number;
name: string;
opening_crawl: boolean;
};
document: {
id: number;
userID: number;
title: string;
brief: string;
status: string;
};
subdocuments: {
id: number;
userID: number;
title: string;
content: string;
status: string;
};
};
}
function useFetchInfo() {
const { loading, error, data } = useQuery<Data>(AllInfoQuery);
return { loading, error, data };
}
export function DocumentScreen() {
const { loading, error, data } = useFetchInfo();
if (loading) {
return <p>Loading...</p>;
}
if (error) {
return <p>There was an error fetching the data!</p>;
}
return (
<PageLayout user={data.user}>
<DocumentHeader document={data.document} />
<DocumentList documents={data.subdocuments} />
</PageLayout>
);
}
此示例使用 Apollo Client 与 GraphQL 发出网络请求。如果您使用的是其他库(例如 URQL 或 React Query),则可以应用相同的原理来模拟 Storybook 中的网络请求。
MSW 插件允许您编写使用 MSW 模拟 GraphQL 请求的故事。以下是如何演示文档屏幕组件的两个故事的示例。第一个故事成功获取数据,而第二个故事失败。
import type { Meta, StoryObj } from '@storybook/react';
import { ApolloClient, ApolloProvider, InMemoryCache } from '@apollo/client';
import { graphql, HttpResponse, delay } from 'msw';
import { DocumentScreen } from './YourPage';
const mockedClient = new ApolloClient({
uri: 'https://your-graphql-endpoint',
cache: new InMemoryCache(),
defaultOptions: {
watchQuery: {
fetchPolicy: 'no-cache',
errorPolicy: 'all',
},
query: {
fetchPolicy: 'no-cache',
errorPolicy: 'all',
},
},
});
//👇The mocked data that will be used in the story
const TestData = {
user: {
userID: 1,
name: 'Someone',
},
document: {
id: 1,
userID: 1,
title: 'Something',
brief: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
status: 'approved',
},
subdocuments: [
{
id: 1,
userID: 1,
title: 'Something',
content:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
status: 'approved',
},
],
};
const meta: Meta<typeof DocumentScreen> = {
component: DocumentScreen,
decorators: [
(Story) => (
<ApolloProvider client={mockedClient}>
<Story />
</ApolloProvider>
),
],
};
export default meta;
type Story = StoryObj<typeof SampleComponent>;
export const MockedSuccess: Story = {
parameters: {
msw: {
handlers: [
graphql.query('AllInfoQuery', () => {
return HttpResponse.json({
data: {
allInfo: {
...TestData,
},
}
});
}),
],
},
},
};
export const MockedError: Story = {
parameters: {
msw: {
handlers: [
graphql.query('AllInfoQuery', async () => {
await delay(800);
return HttpResponse.json({
errors: [
{
message: 'Access denied',
},
],
});
}),
],
},
},
};
为故事配置 MSW
在上面的示例中,请注意每个故事如何使用 parameters.msw
配置来定义模拟服务器的请求处理程序。由于它以这种方式使用参数,因此它也可以在 组件 甚至 项目 级别进行配置,允许您在多个故事中共享相同的模拟服务器配置。