1.Giới thiệu
Storybook là một tiện ích mã nguồn mở cho phép lập trình viên có thể xây dựng và kiểm thử các thành phần giao diện một cách độc lập, tách biệt với phần còn lại của hệ thống.
Với khả năng kiểm tra giao diện một bộ phận nhưng không cần chạy cả hệ thống, nó giúp công việc kiểm thử giao diện trở nên dễ dàng nhanh chóng.
2.Tại sao nên sử dụng storybook
Vấn đề
Xu hướng phát triển frontend hiện giờ (vue, react...) chuộng mô hình chia nhỏ giao diện thành các thành phần (component) để dễ tái sử dụng.
Tuy nhiên một hệ thống thực tế sẽ có rất nhiều component. Component cha gọi component con, con gọi cháu, cháu gọi chắt... khiến cho việc kiểm thử 1 bộ phận nhỏ của giao diện nhiều lúc không hề dễ dàng nếu nó nằm sâu trong gia phả component.
Hơn nữa nhiều màn hình không thể truy cập ngay lập tức mà phải trải qua nhiều màn hình trung gian, biến đổi dữ liệu đầu vào, lấy dữ liệu api bên thứ 3, lấy giá trị ngữ cảnh (context) v.v... khiến việc tạo dữ liệu kiểm thử trở nên khó khăn.
Chưa kể chúng ta còn phải tính tới việc hiển thị component trên các kích thước màn hình khác nhau.
Giải pháp
Ưu điểm của component là chúng ta không cần phải dựng cả hệ thống mới thấy được chúng render ra sao. Bằng cách truyền giá trị, giả lập dữ liệu ta có thể render được một trong các kịch bản mà component có thể hiển thị được.
Storybook tạo ra một iframe cô lập, bọc lấy component mà không làm ảnh hưởng hay thay đổi đến logic của hệ thống. Nó giúp chúng ta tập trung kiểm tra các kịch bản hiển thị khác nhau của component, kể cả các trường hợp hiếm gặp.
3.Lợi ích
1.Phát triển component một cách độc lập
Storybook không chỉ dùng trong giai đoạn kiểm thử sau khi code xong, mà còn có thể sử dụng ngay trong quá trình phát triển.
Để kiểm tra component, bình thường chúng ta sẽ tạo ra 1 màn hình hoàn chỉnh và gọi các component để hiển thị. Với storybook, chúng ta chỉ cần import component vào và viết kịch bản là có thể kiểm tra ngay lập tức ngay cả khi component đấy chưa được sử dụng ở màn hình nào.
2.Test được các trường hợp biên, hiếm gặp một cách dễ dàng
Storybook cung cấp nhiều tiện ích để giả lập dữ liệu:
- Giả lập dữ liệu props
- Giả lập provider
- Giả lập dữ liệu redux, dữ liệu api thông qua các addon. Tham khảo Addon
- Giả lập kích thước màn hình hiển thị.
3.Hệ thống component lại như một dạng tài liệu
Storybook tập hợp các component lại thành một bộ danh mục, mỗi mục component gồm nhiều trạng thái/kịch bản khác nhau giúp việc tra cứu và phát triển dễ dàng hơn.
4.Tự động hóa quy trình phát triển giao diện
Storybook tương thích với mô hình tích hợp liên tục (continuous integration). Thêm storybook vào bước CI để có thể tự động chạy kiểm thử giao diện. Tham khảo Testing
Demo storybook
Dưới dây là một demo nho nhỏ để minh họa storybook.
Cài đặt
Ứng dụng dùng framework Nextjs 13. Template admin lấy từ: next13-horizon-admin-dashboard-ts-tailwind
Bước đầu tiên là clone repo trên:
git clone https://github.com/ichsankurnia/next13-horizon-admin-dashboard-ts-tailwind
Cài đặt các thư viện cần thiết, ở đây mình dùng yarn:
yarn install
Tiếp theo là tích hợp storybook:
npx storybook@latest init
Sau cùng chúng ta chạy lệnh sau để khởi động storybook:
yarn storybook
Nếu mọi chuyện êm xuôi thì terminal sẽ in kết quả như sau:
➜ yarn storybook
yarn run v1.22.19
$ storybook dev -p 6006
@storybook/cli v8.1.10
info Found existing addon "@storybook/addon-viewport", skipping.
attention => Storybook now collects completely anonymous telemetry regarding usage.
This information is used to shape Storybook's roadmap and prioritize features.
You can learn more, including how to opt-out if you'd not like to participate in this anonymous program, by visiting the following URL:
https://storybook.js.org/telemetry
info Found existing addon "@storybook/addon-viewport", skipping.
info => Serving static files from ./.\public at /
info => Starting manager..
info => Starting preview..
info Addon-docs: using MDX3
info => Using implicit CSS loaders
info => Using Babel as compiler
info => Using default Webpack5 setup
<i> [webpack-dev-middleware] wait until bundle finished
10% building 0/3 entries 6/8 dependencies 0/5 modulesinfo Using tsconfig paths for react-docgen
11% building import loader ./node_modules/@storybook/nextjs/dist/next-image-loader-stub.jsYou have to install sharp in order to use image optimization features in Next.js. AVIF support is also disabled.
╭──────────────────────────────────────────────────╮
│ │
│ Storybook 8.1.10 for nextjs started │
│ 919 ms for manager and 8.19 s for preview │
│ │
│ Local: http://localhost:6006/ │
│ On your network: http://192.168.1.2:6006/ │
│ │
╰──────────────────────────────────────────────────╯
<i> [webpack-dev-middleware] wait until bundle finished: /runtime~main.iframe.bundle.js
<i> [webpack-dev-middleware] wait until bundle finished: /vendors-node_modules_pmmmwh_react-refresh-webpack-plugin_client_ReactRefreshEntry_js-node_mod-11d5ae.iframe.bundle.js
<i> [webpack-dev-middleware] wait until bundle finished: /main.iframe.bundle.js
Hiển thị lần chạy đầu
Màn hình storybook cũng sẽ được hiển thị trên trình duyệt với đường dẫn localhost:6006:
Storybook cung cấp một số component mẫu để chúng ta có thể vọc lúc đầu.
Login | Logout |
---|---|
Tạo storybook mới
Chúng ta sẽ tạo 1 storybook khác, mô phỏng 1 trong các component của trang admin.
Đối tượng sẽ là trang đăng nhập.
Đây là giao diện khi hiển thị bình thường, gồm phần form bên trái và một số thông tin kèm đường dẫn khác ở bên phải.
Chúng ta sẽ chỉ test phần form, không đụng tới các phần khác.
Để thuận tiện cho test, mình đã tách phần lớn nội dung trong file app/auth/page.tsx sang components/auth/LogiForm.tsx để minh họa việc giả lập dữ liệu cho component.
Đây là code của app/auth/page.tsx sau khi đã tách:
const [email, setEmail] = useState('');
const [emailError, setEmailError] = useState('');
const [password, setPassword] = useState('');
const [passwordError, setPasswordError] = useState('');
const onEmailChange = (value: string) => {
setEmail(value);
if (!value || EMAIL_REGEX.test(value)) {
setEmailError('');
return;
}
setEmailError('Wrong email format');
return;
};
const onPasswordChange = (value: string) => {
setPassword(value);
if (!value || value.length >= 8) {
setPasswordError('');
return;
}
setPasswordError('Password must be at least 8 characters');
return;
};
return (
<>
<LoginForm
email={email}
setEmail={onEmailChange}
emailError={emailError}
password={password}
setPassword={onPasswordChange}
passwordError={passwordError}
/>
</>
);
LoginForm sẽ nhận các props liên quan đến 2 trường nhập dữ liệu cho email và password, bao gồm
- Nội dung nhập (valkue)
- Hàm onChange
- Thông báo lỗi
Đây sẽ là các dữ liệu cần giả lập.
Nội dung LoginForm chúng ta sẽ giữ gần như nguyên vẹn, chỉ thay đổi một chút ở phần input để có thể nhận props gửi xuống từ components cha:
<InputField
variant='auth'
extra='mb-3'
label='Email*'
placeholder='mail@simmmple.com'
id='email'
type='text'
value={email} // giá trị người dùng nhập
onChange={setEmail} // hàm onChange
state={emailError ? 'error' : email ? 'success' : ''} // trạng thái lỗi/thành công để thay đổi css
error={emailError} // thông báo lỗi
/>
Tạo file storybook theo đường dẫn sau đây: stories/auth/LoginForm.stories.tsx
Đây sẽ là thư mục chứa các story về xác thực.
File storybook tuân theo định dạng <tên_file>.stories.tsx
const meta = {
title: 'components/auth/LoginForm',
component: LoginForm,
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
tags: ['autodocs'],
parameters: {
// More on how to position stories at: https://storybook.js.org/docs/configure/story-layout
layout: 'centered',
},
decorators: [
(Story) => (
<div className='flex min-h-screen items-center justify-center dark:bg-neutral-700'>
<Story />
</div>
),
],
} satisfies Meta<typeof LoginForm>;
meta là đối tượng chứa thông tin về Story:
- title: tên của Story theo dạng đường dẫn.
- component: component cần test, ở đây là LoginForm
- tags: kiểm soát các story nào sẽ được đưa vào storybook. autodocs ở trên sẽ tự động sinh ra 1 story tài liệu, chứa tất cả các kịch bản của component hiện tại
- parameters: tham số nhằm điều chỉnh cách hiển thị của component bên trong trang storybook
- decorator: bọc story trong một element. Mục đích để thay đổi css component cha, mock provider, mock context
Tiếp đến chúng ta sẽ định nghĩa kịch bản.
Kịch bản đầu tiên sẽ là form mặc định hiển thị lên khi người dùng vừa ghé thăm trang đăng nhập, chưa thao tác gì:
export default meta;
type Story = StoryObj<typeof meta>;
const baseArgs = {
email: '',
setEmail: () => {
return;
},
emailError: '',
password: '',
setPassword: () => {
return;
},
passwordError: '',
} as LoginFormProps;
export const Default: Story = {
args: baseArgs,
};
Ở đây chúng ta để email, password và thông báo lỗi trống.
Kịch bản thứ 2 sẽ là kịch bản người dùng nhập lỗi:
export const Error: Story = {
args: {
...baseArgs,
email: 'abc',
emailError: 'Wrong email format',
password: '123123',
passwordError: 'Must be at least 8 characters'
},
};
Kịch bản thứ 3 sẽ là kịch bản người dùng nhập không lỗi:
export const Success: Story = {
args: {
...baseArgs,
email: 'test@test.com',
emailError: '',
password: '123123123',
passwordError: '',
},
};
Để test kích thước màn hình, chúng ta có thể cài đặt addon và thực hiện như video bên dưới:
Addon thay đổi kích thước màn hình: View port
Ngoài ra nếu không muốn tạo nhiều kịch bản, chúng ta có thể thay đổi thông số addon
4.Kết luận
Storybook là một công cụ tuyệt vời tạo và kiểm thử giao diện. Storybook ngày càng được các lập trình viên sử dụng nhiều hơn.
Với hơn 2 ngàn người đóng góp, 22.52 triệu lượt tải/tuần (tính tới thời điểm viết bài) thì mình tin storybook trong tương lai sẽ còn phát triển hơn nữa, giúp cho anh em dev làm việc hiệu quả hơn.