
LIFF MockでStorybookにLIFFアプリコンポーネントを作成する
この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
はじめに
アノテーション株式会社 LINE/DevOpsチームの草竹です!
この記事では、LIFFアプリ(LINEミニアプリ)のUIコンポーネントをStorybookで管理してみた流れを紹介します。
外部と通信するコンポーネントをStorybookで管理する際に必要になってくるのがリクエストのモック化です。
MSWを用いることが多いのではないでしょうか。
今回は、LIFF APIをモック化する手段として公式プラグインのLIFF Mockを使ってみました。
この記事で紹介する内容
- LIFF Mockの使い方
- StorybookでLIFF Mockを扱う方法
概要
- LIFF Mockとは
- LIFF Mockのセットアップ
- Storybookとの統合
- 実際の使用例
- まとめ
動作環境
| 項目 | 値 | 
|---|---|
| @line/liff | 2.24.0 | 
| @line/liff-mock | 1.0.3 | 
| storybook | 8.2.7 | 
| react | 18.3.1 | 
| vite | 5.3.5 | 
| typescript | 5.5.4 | 
LIFF Mockとは
LINE Developers より引用。
LIFF Mockは、LIFFアプリのテストを簡単にするためのLIFFプラグインです。LIFF Mockを使うと、LIFF SDKにモックモードを追加できます。モックモードでは、LIFFアプリがLIFFサーバーから独立し、LIFF APIがモックデータを返すため、単体テストや負荷テストをより簡単に行うことができます。[1]
LIFF Mockは公式LIFFプラグインの一つ[2]です。LIFFアプリの初期化 liff.init() 時に {mock: true} を付与するだけでかんたんに始めることができます。
LIFF Mockのセットアップ
インストール手順はLIFF MockのGitHubでも紹介されていますので、あわせてご確認ください。
パッケージをインストールします。
npm i @line/liff @line/liff-mock
line/liff-mock のサンプルコードを参考に、 LIFF の型を拡張します。
liff.init() のオプションに mock を追加したり、liff.$mock を使えるようにTypeScriptに認識させるため必要です。
import { ExtendedInit, LiffMockApi } from "@line/liff-mock";
declare module "@line/liff" {
  interface Liff {
    init: ExtendedInit;
    $mock: LiffMockApi;
  }
}
LIFFアプリの初期化関数をモジュール化しておきます。
// LIFF Mock
import { liff } from "@line/liff";
import LiffMockPlugin from "@line/liff-mock";
export const liffInit = async () => {
  liff.use(new LiffMockPlugin());
  await liff
    .init({ liffId: import.meta.env.VITE_LIFF_ID, mock: true })
  // liff.login() が呼ばれている必要があるため
  if (!liff.isInClient()) liff.login()
};
これでLIFF APIのモック化ができるようになりました!
モックデータをセットするには、[メソッド, 戻り値]の[key, value]を liff.$mock.set() に渡してあげればOKです。
今回の例ではLINEプロフィールを表示するメソッド liff.getProfile() をモック化してみます。
このように、getProfile に対して、返してほしい値を渡してあげます。
liff.$mock.set({
  getProfile: {
    displayName: 'KUSATAKE Daisuke',
    userId: '123456789',
    pictureUrl,
    statusMessage: 'お昼休みはもくもくモッキング',
  }
})
Storybookとの統合
StorybookにLIFF Mockを統合するためにやるべきことが2つあります。
- liff.init()でLIFFアプリの初期化
- liff.$mock.set()でモックデータのセット
私は次のように行いました。
| LIFF API | Storybook関連ファイル | 実行タイミング | 
|---|---|---|
| liff.init() | .storybook/preview.ts | - | 
| liff.$mock.set() | .src/**/*.stories.ts | Story.beforeEach | 
順を追って説明します。
 1. liff.init() でLIFFアプリの初期化
liff.init() での初期化は、プロダクションコードと同様に、他のLIFF APIが実行されるより前に行っておく必要があります。
通常、src/main.tsx 等のエントリーポイントで liff.init() の実行完了を待ってからDOMレンダリングするなどするでしょう。
Storybookでは各ストーリー *.stories.ts のエントリーポイントは .storybook/preview.ts です。ここで初期化します。[4]
import type { Preview } from "@storybook/react";
import { liffInit } from "../src/lib/liff-init.mock.ts";
import "src/index.css";
await liffInit()
const preview: Preview = {};
export default preview;
 2. liff.$mock.set() でモックデータのセット
次に、各ストーリーで liff.$mock.set() を実行し、本来LIFFサーバーから得る値を任意のモックデータに改竄します。
Story.beforeEach はStoryが呼び出される前に実行されるため、ここでStoryごとにモックデータをセットします。
// ログインしていて、ユーザープロフィールが取得できているシナリオ
export const LoggedIn: Story = {
  beforeEach: () => {
    liff.$mock.set({
      getProfile: {
        userId: "U1234567890",
        displayName: "草竹 大輔",
        pictureUrl,
        statusMessage: "お昼休みはもくもくモッキング",
      },
    });
  },
};
// ログインされておらず、ユーザープロフィールが取得できていないシナリオ
export const LoggedOut: Story = {
  beforeEach: () => {
    liff.$mock.set({
      getProfile: {
        isLoggedIn: false,
      }
    });
  },
};
これをliff.getProfile()を内部で実行しているコンポーネントのストーリーとして適用させます。
サンプルコンポーネントとして UserProfile.tsx を作成しました。
Storybookを立ち上げて確認すると、モックデータをセットできていることがわかります!モックデータ通りのプロフィール情報が表示されていますね。

他、任意のコンポーネント・LIFF APIについても同様の手順でモックデータをセットすれば実現可能なはずです。
まとめ
LIFF MockとStorybookを統合する際、2つのポイントに注意しました。
- LIFF Mockの初期化は .storybook/preview.tsで行う
- LIFF Mockのモックデータのセットは各ストーリーファイル src/**/*.stories.tsで行う
最後までお読みいただきありがとうございました。
今回作成したサンプルプロジェクトを添付します。参考になれば幸いです。
アノテーション株式会社について
アノテーション株式会社はクラスメソッドグループのオペレーション専門特化企業です。
サポート・運用・開発保守・情シス・バックオフィスの専門チームが、最新 IT テクノロジー、高い技術力、蓄積されたノウハウをフル活用し、お客様の課題解決を行っています。
当社は様々な職種でメンバーを募集しています。
「オペレーション・エクセレンス」と「らしく働く、らしく生きる」を共に実現するカルチャー・しくみ・働き方にご興味がある方は、アノテーション株式会社 採用サイト をぜひご覧ください。
- 2024/08/05 現在、他にLIFF Inspectorがあります。 ↩︎ 
- LIFF Mockの実装を参照すると、 - liff.login()が呼ばれていることが前提となることがわかります。 ↩︎














