153
139

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

REST APIを簡単にMockできるツールSmopeckの紹介

Last updated at Posted at 2020-04-25

はじめに

最近のウェブアプリではバックエンドをREST-APIとして用意し、
フロントエンドはREST-APIから引っ張ってきたデータをReactやVueといったフレームワークで描画することが多いと思います。

このようなウェブアプリを開発する際に問題となるのはバックエンドとフロントエンドを並行して開発しにくいということです。バックエンドができなければフロントエンドはどんなデータが来るのかわからず描画できませんし、バックエンドもフロントエンドからどのようなリクエストが来るか決まらないと実装ができません。

そのため、最初にREST-APIの仕様を定めて、その仕様に沿ったモックサーバを作成し、
フロントエンドはバックエンドが完成するまでそれを用いて開発を進めるということが行われます。

さてそのREST-APIの仕様とはどのように記述されるのでしょうか?

1. 自然言語で記述する

一番よくある場合だと思います。自然言語では非常に柔軟な仕様を記述することができます。
ただし、その解釈は一通りとは限らないので、認識のズレから不整合が起こることもあります。

2. gRPC等で記述する

gRPCの時点でREST-APIとは正確には異なりますが、機械が読める形で仕様を記述できるので認識のズレは起こりにくいです。
ただ、記述できる仕様はデータの基本的な型のみでそこまで柔軟ではありません。

また、どちらの場合であっても、モックサーバを実装するのにも多少の時間がかかります。
また、モックサーバの入出力が本当に仕様を守っているのかは検証しないことの方が多いでしょう。

私はここでの第3の選択肢となるツールSmopeckを開発しました。
https://github.com/autotaker/smopeck

ツールはまだプロトタイプですが、それなりに使えるようになってきたので紹介していきます。

Smopeck

Smopeckの特長は以下の通りです

  • 仕様を型で書くとその仕様のREST-APIをモックするサーバができます。
  • 依存型や正規表現をサポートすることで柔軟な仕様が記述できます。

デモ

Hello World!

試しにHello Worldを作成してみましょう。

hello.spec
endpoint "/hello" GET {
  response : JsonResponse {
    body: String [ . = 'hello world!' ]
  }
}

これを入力として渡してサーバを立ち上げます。

cabal new-exec smopeck mock --host localhost --port 8888 hello.spec

これだけでREST-APIができています。試しにアクセスしてみるとちゃんとレスポンスが返ってきます。

$ curl -s http://localhost:8888/hello | jq .
"hello world!"

Profile

次は /profileでユーザのIDとnameを取得するAPIを作ってみましょう。
ただし、次の仕様を満たす必要があるとします。

  • ユーザIDは正の整数である
  • ユーザ名は英数字のみからなり1文字以上10文字以下である。
profile.spec
endpoint "/profile" GET {
    response: JsonResponse {
        body: Object {
            id : Int [ . > 0 ],
            name : String [ . =~ r'[a-zA-Z0-9]{1,10}' ]
        }
    }
}

これだけです。[ ]の内側にデータが満たすべき述語がかけるので非常に直感的に書くことができます。

現在サポートしている述語演算子は 等号, 比較演算子(整数型のみ), =~ (文字列型のみ)です。

サーバを再起動するとREST-APIができます。

$ curl http://localhost:8888/profile -s | jq .
{
  "name": "nNgDWJho",
  "id": 93
}
$ curl http://localhost:8888/profile -s | jq .
{
  "name": "aaKCZgzOLD",
  "id": 9
}
$ curl http://localhost:8888/profile -s | jq .
{
  "name": "x",
  "id": 86
}
$ curl http://localhost:8888/profile -s | jq .
{
  "name": "xbk",
  "id": 61
}

smopeckでは仕様を満たす値が複数ある場合はそれらをランダムに生成します。

Request Parameter

次は検索API /searchを作ってみましょう。

仕様

  • GETパラメータkeyで検索文字列を受け取る
  • 返り値はオブジェクトのリストで長さは10未満
  • リストの各オブジェクトは 整数型フィールドidと 文字列型フィールドcontentを持つ
  • idフィールドの値は1以上
  • contentフィールドの値は keyを部分文字列として含む。

この仕様をsmopeckで記述するとこうなります。

search.spec
endpoint "/search" GET {
    parameter: Parameter {
        query : Object {
            key : String 
        }
    },
    response: JsonResponse {
        body: Array {
            length: Int [ . >= 0, . < 10 ],
            get(i): Object {
                id: Int [ . > 0 ],
                content: String [ . =~ r'.*' + parameter.query.key + r'.*' ]
            }
        }
    }
}

リストはArray型で表現できます。Array型では lengthで長さの指定が
get(i)で、i番目の要素の指定ができます。

parameter部でGETパラメータの仕様を書くとparameter.query.keyで利用できるのがポイントです。

実行してみましょう。

$ curl http://localhost:8888/search?key=hoge -s | jq .
[
  {
    "content": "hhohh?o%hohogeho",
    "id": 69
  },
  {
    "content": "thogehhhhoh9",
    "id": 9
  },
  {
    "content": "hi_4\\hhah hohhoghoge",
    "id": 75
  },
  {
    "content": "hhoge",
    "id": 39
  },
  {
    "content": "hhshoghhoh]h,hov7hLhog>hohoge",
    "id": 15
  },
  {
    "content": "|/hhhhho3nhhogeh",
    "id": 73
  },
  {
    "content": "hohhho\"hoge",
    "id": 47
  }
]

生成される文字列に検索文字列が含まれていることが確認できると思います。1

Form Validation

今度はPOSTを行うAPIを作ってみましょう。

Emailをアドレスを登録するAPIを作ってみることにします。

仕様

  • パスは /user/${id}/profile/emailここで ${id}はユーザのIDを表す1以上の整数
  • 送信データはJSONオブジェクトで、文字列型のemailフィールドを持つ
  • emailフィールドの値はemailアドレスでなければならない。
form.spec
endpoint "/user/:id/profile/email" POST {
    parameter : Parameter {
        path: Object {
            id : Int [ . > 0]
        }
    },
    request: JsonRequest {
        body: Object {
            email: String [ . =~ r'[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*' ]
        }
    },
    response: JsonResponse
}

parameter部で pathパラメータを設定すると、RESTパスの:idInt型としてパーズしてくれます。emailアドレスは簡単な正規表現でチェックしています。参考

smopeckは実際に来たリクエストがrequest部に書かれた仕様を満たしているか検査します。
検査に通ればレスポンスを返しますが、通らなければ400エラーを返します。

$ curl http://localhost:8888/user/100/profile/email -X POST -H 'Content-Type: application/json' -d '{ "email": "autotaker@example.com" }' -L -s -w '%{http_code}\n' -o /dev/null
200
$ curl http://localhost:8888/user/100/profile/email -X POST -H 'Content-Type: application/json' -d '{ "email": "autotaker@exam@ple.com" }' -L -s -w '%{http_code}\n' -o /dev/null
400

smopeckの使い道

「はじめに」でも述べましたが、smopeckはWEBアプリケーションのフロントエンドを開発する際のモックサーバとして使うことを想定していますが以下の目的でも使えるでしょう。

  • 外部APIを利用するアプリケーションのテスト用のモックサーバ
  • テスト用データの自動生成

今後

smopeckでは今後、以下の機能を実装していく予定です。

  1. フロントエンドとバックエンドの通信の間に入って立ち、バックエンドで実装されていないAPIのみをモックするProxy機能
  2. リクエストの仕様に従った値を自動生成してバックエンドサーバに送信し、結果がレスポンスの仕様にあっているかを自動テストする機能
  3. .specファイルの構文およびサポートする演算子の拡充
  1. 生成される文字列にhが多いのは現在の生成アルゴリズムの分布に偏りがあるためです。

153
139
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
153
139

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?