見出し画像

Mockoonでつくったモックサーバーがもたらす安心な開発体験

🎄この記事はNAVITIME JAPAN Advent Calendar 2023の20日目の記事です。

こんにちは、みみぞうです。
ナビタイムジャパンで『システムや開発環境、チームの改善』を担当しています。

今回はMockoonというWeb APIのモックサーバー作成ツールをご紹介します。

本稿は以下のような方をターゲットにしています。

  • Web APIの開発が終わっていなかったため、APIを呼び出す処理が実装できずに困ったことがある方

  • 社外のWeb APIと結合してから問題が発覚し、問題になることが多い方

  • Web APIとの接続部分があるためテストが書けない...とお困りの方

Mockoonとは

Mockoonは簡単にREST APIのモックサーバーを構築できるツールです。ローカルだけで動作し、無料であり、オープンソースです。

Mockoon is the easiest and quickest way to design and run mock REST APIs.
No remote deployment, no account required, free and open-source.

Create mock APIs in seconds with Mockoon より引用

本稿ではMockoonについてご紹介します...がその前に、モックサーバーとその必要性について説明させてください。

モックサーバーとは

モックサーバーとは、本物と同じようにふるまう擬態したサーバーのことです。本稿では、リクエストに対するレスポンスが本物のように動くWeb APIのことを指します。

たとえば、フリーワードを指定して書庫を検索できるWeb APIを考えてみましょう。利用者から見るとこんな風に見えると思います。

書庫を検索できるWeb API

実際には、書庫Web APIはデータベースなどを利用してデータを検索し、その結果を加工して返したりすることが多いでしょう。

実際のシステム構成

実際のシステム構成に対し、モックサーバーを使ったシステム構成は以下のようになります。

モックサーバーの一例

『リクエストから本物っぽいレスポンスを生成する方法』に決まりはありません。DBの代わりにファイルを読み込んで結果を返却してもいいですし、ハードコーディングでひたすらif文を書いてもいいです。場合によっては、検索条件を無視してすべて同じ結果を返すのもアリです。

なぜモックサーバーが必要なのか

はじめてモックサーバーの存在を知ったとき、私はこう思いました。

『結局、本物を作るのに、なぜそれっぽい偽物を作らなければいけないのか? それって時間の無駄じゃないですか??』

たしかにそうかもしれません。自分で開発する/できるものであれば、はじめから本物を作った方がいいでしょう。モックサーバーを作る価値はあまりないと思います。しかし、自分で開発しない/できないもので、自分が作るシステムと連携しなければいけない場合ならどうでしょうか?

あなたが開発する『書籍検索システム』が、別の会社が開発する『書庫Web API』と連携するシーンを想像してみましょう。

別の会社が開発する書籍Web APIと連携するシステム開発

この『書庫Web API』が、開発当初から存在し、好きなだけ数を増やせて、24時間365日完璧に動き、いつでも環境をリセットできる... というのであればモックサーバーは必要ないかもしれません。ただ、実際はそう上手くいかないことも多いと思います。

  • 仕様書はあるが、実際にWeb APIを利用できるのは数ヶ月後と言われた

  • 開発用のWeb APIは不安定であり、平日の日中数時間しか起動していない

  • 開発用のWeb APIを動かすサーバーが1つしかないため、検証業務中に開発目的で利用できなかったり、開発中システムの自動テストができない

あなたからすれば『仕様通りに動く「書庫Web API」と連携して、「書籍検索システム」が動くことを確認したい』だけ...。それが叶うのであれば、実際にリリースされるサービスと同じモノを使う必要はないはずです。モックサーバーを使いましょう!

モックサーバーなら...

  • 仕様書通りにふるまわせて、すぐにWeb APIを利用した開発ができる...!!

  • 好きな時にいつでもアクセスできる...!!

  • 好きなだけ立ち上げることができる...!!

なぜMockoonなのか

モックサーバーを作る方法は他にも選択肢があります。

  • if文などを用いたハードコーディング

  • ファイル読み込みによる実装

  • モックサーバー構築ライブラリを使ってコードを書く方法

  • 設定ファイルを書いてモックサーバーツールを起動する方法

それなのに、Mockoonを紹介する主な理由は以下の2点です。

  • 使いやすいGUIツールでふるまいを設定できる

  • 多機能であり、ほとんどの要件に対応できる

これらのメリットを実感していただくため、これから実際に『書庫Web API』をモックサーバー化して、『書籍検索システム』のコードと結合してみます。

インストールと起動

公式サイトのチュートリアルを参考にインストールします。

私はUbuntu(WSL)を使っているため、snapコマンドでインストールしました。

sudo sunap install mockoon

起動します。

/snap/mockoon/current/mockoon

初回起動後にはDemo環境を作成されるか聞かれます。承諾すると以下のような画面になると思います。

スタイリッシュな外見... 胸が高鳴りますね😊

環境を作成する

今回は『書庫Web API』のモックサーバーを作りたいので、Demoとは別の環境を作成します。環境(environment)はWeb APIごとの設定みたいなものであり、実体はJSONファイルです。

左上のアイコンから環境を作成

archive-api.json という名前で作成してみました。すると新しく作成した環境が開かれます。

書庫検索APIのふるまいを定義する

早速ふるまいを定義してみましょう。仕様書で以下のように書かれていたとします。

| 項目        | 定義              |
| ----------- | ----------------- |
| HTTP Method | GET               |
| path        | /archive/search   |
| query       | word (検索ワード) |

レスポンスは以下のようなJSON形式であり、成功のステータスは200// word=ナビタイム の場合
{
  "word": "ナビタイム",
  "results": [
    {
      "id": 2010,
      "name": "ナビタイムジャパンの歴史"
    },
    {
      "id": 2020,
      "name": "配達ナビタイムを使ってみよう"
    }
  ]
}

wordが未指定の場合はステータス400で以下のJSONを返す。

{
  "error": {
    "message": "wordは必須パラメーターです"
  }
}

リクエストの定義

まずはHTTP Methodとpathを定義します。こう書くだけで `GET /archive/search` がリクエストされたら、この設定で定義されたレスポンスを返却できるようになります。

実際に動いているモノを見ないとイメージがわかないと思いますので、今は黙々と設定を追加していきましょう。次はqueryの設定です。

このように設定すると『クエリパラメーターのwordが指定されたとき、Response 1 (200) というレスポンス定義を返す』ようなふるまいになります。

レスポンスの定義

次にレスポンスのステータスとJSONを定義しましょう。

今回はinline(直接記入)でレスポンスを記載しましたが、fileを選ぶと、パスで指定したファイルを読み込めたりもします。

また、wordには {{queryParam 'word'}} というテンプレートのような文字列が指定されています。これは『クエリパラメーター wordの値をレスポンスとして含める』という意味です。この表現にはRequest helperのqueryParamという機能を使っています。

条件分岐や文字列加工などの高度な機能もありますので、一度目を通しておくと作業が捗ることでしょう。

異常系の定義

先ほどの定義は正常系です。異常系も忘れずに設定しましょう。

wordが未指定の場合はステータス400で以下のJSONを返す。

{
  "error": {
    "message": "wordは必須パラメーターです"
  }
}

異なるレスポンスを定義するには、レスポンスセレクタの左にあるボタンを押します。

すると、『Response 2 (200)』が作成されます。リストをクリックするとレスポンス一覧を確認・選択できます。

要領は先ほどと同じです。今回は先にレスポンスを定義します。

クエリパラメーターの条件は先ほどとは逆で、『wordが指定されなかったとき』となります。

not条件を示す『 ! 』が紛らわしいのですが、青く点灯しているとき(選択状態)のみ有効です。上の画像では青くないのでnot条件にはなりません。

モックサーバーを起動して動作を確認する

さて、お待ちかね、動作確認の時間がやってまいりました👏 起動は簡単。左上の再生みたいなボタンを押すだけです。

起動に成功するとボタンのマークが変わります。もう一度押すとサーバーを停止します。

正常系の挙動を確認する

http://localhost:3001/archive/search に word を指定してアクセスしてみます。

$ curl "http://localhost:3001/archive/search?word=NAVITIME"
{
  "word": "NAVITIME",
  "results": [
    {
      "id": 2010,
      "name": "ナビタイムジャパンの歴史"
    },
    {
      "id": 2020,
      "name": "配達ナビタイムを使ってみよう"
    }
  ]
}

期待通りのレスポンスが返ってきました!

異常系の挙動を確認する

次に、wordを指定せずリクエストしてみましょう。

$ curl "http://localhost:3001/archive/search"
{
  "error": {
    "message": "wordは必須パラメーターです"
  }
}

こちらも期待通りですね😆

定義されていないpathにリクエストする

最後は存在しないpathにアクセスしてみます。Mockoonが用意している404エラーのHTMLが返却されます。

$ curl "http://localhost:3001/hogehoge"
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>Cannot GET /hogehoge</pre>
</body>
</html>

ログを見る

今回のような簡単な設定であれば、期待通り動かなかった場合の原因究明もそこまで大変ではありません。ただ、実際のシステムをモックサーバー化すると、定義もかなり複雑になると思います。その挙動が期待通りでなかったときに原因を調べるのは手間でしょう。

Mockoonにはモックサーバーへのリクエストと、そのとき返却したレスポンスをログとして記録する機能があります。デフォルトでは有効になっており、Logsタブから閲覧できます。

また、リクエストごとに『Mockoonの定義にマッチしてモックサーバーとして応答されたか?』を示すマークが表示されます。

期待通り動かなかった場合は、実際にリクエストされているか? リクエストの内容が定義とマッチしなかったのはなぜか? といったことを確認してみましょう。

テストコードを書いてみる

こちらが期待する通り... それでいて仕様通り動く『書庫Web API』のモックサーバーができました。最後に『書籍検索システム』のコードとテストコードを書いてみます。

コードを書く

TypeScriptでsearchBooks関数を書いてみました。今回の話にフォーカスするため、多少歪な実装になっておりますのでご了承ください。

interface Response {
  word: string;
  results: {
    id: number;
    name: string;
  }[];
}

const BASE_URL = "http://localhost:3001";

export async function searchBooks(word: string): Promise<Response> {
  const params = new URLSearchParams(word ? { word } : {}).toString();

  const res = await fetch(`${BASE_URL}/archive/search?${params}`);
  if (res.status === 400) {
    throw new Error((await res.json()).error.message);
  }

  return res.json<Response>();
}

テストコードを書く

今回はBunを使っているので bun:test を利用します。

import { expect, test } from "bun:test";
import { searchBooks } from ".";

test("wordを指定して書籍を検索できる", async () => {
  const actual = await searchBooks("NAVITIME");
  expect(actual.word).toBe("NAVITIME");
  expect(actual.results).toStrictEqual([
    { id: 2010, name: "ナビタイムジャパンの歴史" },
    { id: 2020, name: "配達ナビタイムを使ってみよう" },
  ]);
});

test("wordを指定しないとパラメーターエラーになる", async () => {
  expect(() => searchBooks("")).toThrow(
    new Error("wordは必須パラメーターです")
  );
});

テストを実行する

それではテストを実行してみます。

$ bun test
bun test v1.0.15 (b3bdf22e)

index.test.ts:
✓ wordを指定して書籍を検索できる [5.48ms]
✓ wordを指定ないとパラメーターエラーになる [4.14ms]

 2 pass
 0 fail
 3 expect() calls
Ran 2 tests across 1 files. [21.00ms]

本物の『書庫Web API』がなくてもテストがちゃんと成功しました✨

まとめ

モックサーバーが必要な理由と、MockoonというWeb APIのモックサーバー作成ツールを使った実践例についてご紹介しました。

Mockoonは本当に多機能です。本記事の内容は、本来ご紹介したかった内容の3割にも満たないと思います。紹介しきれなかったものの概要だけでも並べてみました。

  • 柔軟な条件設定 (AND/OR、正規表現、何回目のリクエストか? など)

  • JSONだけでなくXMLにも対応している

  • Request helperのようなテンプレート機能(変数)があらゆる場所で使える

  • GUIだけでなく、npmやDocker経由でCLIとして利用できる

  • Data bucketを使ってデータのみに集中した定義ができる

  • Auto-mocking and recording機能を使って実際のリクエストを漏れなく定義できる

  • OpenAPI specificationと連携できる

  • Faker.jsを使ってサンプルデータを一瞬で作れる

  • 定義を厳しく書くことで実際のシステムと結合したときに使用漏れや実装漏れが一瞬で分かる

もし反響がありましたら続編を書きたいと思っています。みなさまもMockoonでモックサーバーをつくり、安心な開発体験を手にしてみてください😄

最後までお読みいただきありがとうございました!