LIFF Mockを使ってみた

# はじめに

[LIFF InspectorとLIFF Mockをリリースされた](https://developers.line.biz/ja/news/2022/04/25/liff-plugin/)とのことで。ローカルでのテストが楽になりそうな[LIFF Mock](https://github.com/line/liff-mock)を早速使ってみることにします。

# やってみる

サンプルコードとしてこちらも最近リリースされた[create-liff-app](https://github.com/line/create-liff-app)を使います。まずはプロジェクトの作成。設定は好みで。

```shell
npx @line/create-liff-app
Need to install the following packages:
  @line/create-liff-app
Ok to proceed? (y) y
Welcome to the Create LIFF App
? Enter your project name:  (my-app) my-app
? Which template do you want to use? react
? JavaScript or TypeScript? TypeScript
? Please enter your LIFF ID: xxxxxx
? Do you want to install it now with package manager? (Y/n) Yes
? Which package manager do you want to use? yarn
```
あとでMock化するのでLIFF IDは適当に`xxxxxx`としておきました

`@line/liff-mock`を追加します

```shell
cd my-app
yarn add @line/liff-mock
 ```

開発環境を起動します

```shell
yarn dev
```

![スクリーンショット 2022-05-03 16.14.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61995/f43ba0c2-4e28-2d31-20e9-2c9601d22c41.png)

[http://localhost:3000/](http://localhost:3000/)にアクセスするとサンプルアプリが表示されます。存在しないLIFF IDを設定したため`liff.ini()`が失敗しています

`src/App.tsx`を以下のように更新してLIFF Mock pluginを追加します

```src/App.tsx
import { useEffect, useState } from "react";
import liff from "@line/liff";
import "./App.css";
import { LiffMockPlugin } from '@line/liff-mock';

liff.use(new LiffMockPlugin());

function App() {
  const [message, setMessage] = useState("");
  const [error, setError] = useState("");

  useEffect(() => {
    liff
      .init({
        liffId: import.meta.env.VITE_LIFF_ID,
        mock: true, // enable mock mode
      })
      .then(() => {
        setMessage("LIFF init succeeded.");
      })
      .catch((e: Error) => {
        setMessage("LIFF init failed.");
        setError(`${e}`);
      });
  });

  return (
    <div className="App">
      <h1>create-liff-app</h1>
      {message && <p>{message}</p>}
      {error && (
        <p>
          <code>{error}</code>
        </p>
      )}
      <a
        href="https://developers.line.biz/ja/docs/liff/"
        target="_blank"
        rel="noreferrer"
      >
        LIFF Documentation
      </a>
    </div>
  );
}

export default App;
```

[npm-typescript-example](https://github.com/line/liff-mock/blob/main/examples/npm-typescript-example/src/liff.d.ts)を参考に`types/liff.d.ts`を追加します。TypeScriptの場合この型定義がないと型エラーが発生します

```types/liff.d.ts
import { ExtendedInit, LiffMockApi } from '@line/liff-mock';

declare module '@line/liff' {
  interface Liff {
    init: ExtendedInit;
    $mock: LiffMockApi;
  }
}
```

LIFF Mockを追加すると`liff.init()`が成功するようになりました。うまくMock化できたようです。

![スクリーンショット 2022-05-03 19.13.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61995/f58275db-0c3c-399f-a1b0-8fb710fa8eaf.png)


Mock化された`liff.getProifle()`を呼んでみます。取得したプロファイルは`setMessage()`を拝借して表示してみます

```App.tsx
import { useEffect, useState } from "react";
import liff from "@line/liff";
import "./App.css";
import { LiffMockPlugin } from '@line/liff-mock';

liff.use(new LiffMockPlugin());

function App() {
  const [message, setMessage] = useState<string>("");
  const [error, setError] = useState("");

  useEffect(() => {
    const f = async () => {
      const options = {
        liffId: import.meta.env.VITE_LIFF_ID,
        mock: true, // enable mock mode
      };
      try {
        await liff.init(options);
        if (!liff.isLoggedIn()) {
          liff.login();
          const profile = await liff.getProfile();
          setMessage(JSON.stringify(profile));
        }
      } catch (e) {
        setMessage("LIFF init failed.");
        setError(`${e}`);
      }
    };
    f();
  });

  return (
    <div className="App">
      <h1>create-liff-app</h1>
      {message && <p>{message}</p>}
      {error && (
        <p>
          <code>{error}</code>
        </p>
      )}
      <a
        href="https://developers.line.biz/ja/docs/liff/"
        target="_blank"
        rel="noreferrer"
      >
        LIFF Documentation
      </a>
    </div>
  );
}

export default App;
```

実行するとダミーのプロファイルが取得できました

![スクリーンショット 2022-05-03 19.47.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61995/700a8c23-4957-8ce5-5276-5e3c941ffc9e.png)

おもしろいので他のメソッドもモック化されているのか試してみました。`scanCodeV2()`を呼んでみます

```src/App.tsx
      try {
        await liff.init(options);
        if (!liff.isLoggedIn()) {
          liff.login();
          const scaned = await liff.scanCodeV2();
          setMessage(JSON.stringify(scaned));
        }
      } catch (e) {
        setMessage("LIFF init failed.");
        setError(`${e}`);
      }
 ```

![スクリーンショット 2022-05-03 19.50.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61995/6e5b0d33-54d8-6713-2c02-8767f52c7f62.png)

QRコードのスキャン結果として`https://line.me`が返ってきました

`liff.sendMessages()`もエラーなく正常に実行されました

```src/App.tsx
      try {
        await liff.init(options);
        if (!liff.isLoggedIn()) {
          liff.login();
          await liff.sendMessages([ { type: "text", text: "Hello, World!", } ]);
          setMessage('Message sent');
        }
      } catch (e) {
        setMessage("LIFF init failed.");
        setError(`${e}`);
      }
```

![スクリーンショット 2022-05-03 20.04.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61995/2e0ae1fa-238c-8493-519b-2ae33db2e2b0.png)


`liff.$mock.set()`を利用してMock APIが返すデータを設定することができます

```src/App.tsx
import { useEffect, useState } from "react";
import liff from "@line/liff";
import "./App.css";
import { LiffMockPlugin } from '@line/liff-mock';

liff.use(new LiffMockPlugin());

liff.$mock.set((p) => ({
  ...p,
  getProfile: { displayName: 'bathtimefish', userId: '123456789' },
}));

function App() {
  const [message, setMessage] = useState<string>("");
  const [error, setError] = useState("");

  useEffect(() => {
    const f = async () => {
      const options = {
        liffId: import.meta.env.VITE_LIFF_ID,
        mock: true, // enable mock mode
      };
      try {
        await liff.init(options);
        if (!liff.isLoggedIn()) {
          liff.login();
          const profile = await liff.getProfile();
          setMessage(JSON.stringify(profile));
        }
      } catch (e) {
        setMessage("LIFF init failed.");
        setError(`${e}`);
      }
    };
    f();
  });

  return (
    <div className="App">
      <h1>create-liff-app</h1>
      {message && <p>{message}</p>}
      {error && (
        <p>
          <code>{error}</code>
        </p>
      )}
      <a
        href="https://developers.line.biz/ja/docs/liff/"
        target="_blank"
        rel="noreferrer"
      >
        LIFF Documentation
      </a>
    </div>
  );
}

export default App;
```

![スクリーンショット 2022-05-03 19.56.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61995/343d22a1-0757-898b-f433-a70a9137d119.png)


`liff.$mock.clear()`は`liff.$mock.set()`で設定したデータをデフォルト値に戻します

```src/App.tsx

liff.$mock.set((p) => ({
  ...p,
  getProfile: { displayName: 'bathtimefish', userId: '123456789' },
}));

....

        await liff.init(options);
        if (!liff.isLoggedIn()) {
          liff.login();
          liff.$mock.clear();
          const profile = await liff.getProfile();
          // {"displayName":"Brown","userId":"123456789","statusMessage":"hello"}
          setMessage(JSON.stringify(profile));
        }
```

現時点でLIFF MockのAPIは以上です

# おわりに

ローカルでLIFFアプリを開発する場合は一部のAPIがローカルで正常に動作しないのを回避するために工夫する必要がありました。LIFF Mockプラグインを使うことでその苦労がなくなります。個人的にはプロトタイピングする前にチャンネルを作ったりLIFF IDやLIFF URLを生成したりする手間がなくLIFF APIを試せるのが気持ちよくなったところです。

これからLIFFアプリを開発しようとする方はcreate-liff-appとLIFF Mockを利用してカジュアルにLIFF APIを試してみるところからはじめるといいんじゃないでしょうか。