模擬網路請求
對於發出網路請求的元件 (例如,從 REST 或 GraphQL API 擷取資料),您可以使用 Mock Service Worker (MSW) 等工具來模擬這些請求。MSW 是一個 API 模擬函式庫,它依賴 Service Workers 來捕獲網路請求,並提供模擬資料作為回應。
MSW 擴充功能將此功能帶入 Storybook,讓您可以在 Stories 中模擬 API 請求。以下是如何設定和使用此擴充功能的概述。
設定 MSW 擴充功能
首先,如有必要,請執行此命令以安裝 MSW 和 MSW 擴充功能
npm install msw msw-storybook-addon --save-dev
如果您尚未使用 MSW,請產生 Service Worker 檔案,這是 MSW 運作所必需的
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;
最後,初始化擴充功能並使用專案層級載入器將其應用於所有 Stories
// 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 請求的 Stories。以下是文件畫面元件的兩個 Stories 範例:一個成功擷取資料,另一個失敗。
// 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 請求的 Stories。以下範例示範了文件畫面元件的兩個 Stories。第一個 Story 成功擷取資料,而第二個 Story 失敗。
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',
},
],
});
}),
],
},
},
};
為 Stories 設定 MSW
在以上範例中,請注意每個 Story 如何使用 parameters.msw
進行設定,以定義模擬伺服器的請求處理程序。由於它以這種方式使用 parameters,因此也可以在元件甚至專案層級進行設定,讓您可以在多個 Stories 之間共用相同的模擬伺服器設定。