darudaru

だるだるしてるエンジニア

FrisbyでREST APIのテスト自動化を試みる

REST APIのテストがめんどくさい、自動化したい、と調べていたら、FrisbyというREST APIのテストフレームワークのことを知ったので試してみた。

シナリオが書けるREST APIのテストツールを探した

意外になくて驚いた。

Frisbyを選んだ理由は、使っている人が割といて情報も多そうだということと、簡単にテストコードが記述できそうという理由から。
ちなみに、Frisbyの読み方は「フリスビー」です。

Get Started

1. インストールする

REST API Testing—Frisby.js

公式ドキュメントに沿って、FrisbyとJasmineをインストールします。

% npm install --save-dev frisby
% npm install -g jasmine-node

2. テストを書く

テストコードを置くディレクトリを作成します。

% mkdir api_test 
% cd api_test

ディレクトリを作ったら、テストコードを置きます。テストコードは***_spec.jsというファイル名にします。
今回は郵便番号-住所検索APIというAPIを使わせていただいて、ステータスコードを確認するテストコードを書いてみました。

var frisby = require('frisby');                                                                                                        
frisby.create('住所検索')
  .get('http://api.zipaddress.net/?zipcode=1000000')
  .expectStatus(200)
.toss();

3. テストを実行する

jasmine-node ディレクトリパスで実行します。

成功したらこうなる。

% jasmine-node . 
Finished in 0.112 seconds
1 test, 14 assertions, 0 failures, 0 skipped

失敗するとこうなる。

% jasmine-node . 
F

Failures:

  1) Frisby Test: 住所検索 
        [ GET http://api.zipaddress.net/?zipcode=1000000 ]
   Message:
     Expected 200 to equal 500.
   Stacktrace:
     Error: Expected 200 to equal 500.
    at .<anonymous> (/Users/username/node_modules/frisby/lib/frisby.js:493:42)
    at .<anonymous> (/Users/username/node_modules/frisby/lib/frisby.js:1074:43)
    at tryOnTimeout (timers.js:232:11)

Finished in 0.123 seconds
1 test, 14 assertions, 1 failure, 0 skipped

なんのテストが、どうして失敗したかを出力してくれます。

Usage

Frisby.js API Documentation—Frisby.js
公式ドキュメントはこちら。英語のドキュメントですが、機能も多くないので読んだらだいたい分かる。

APIを叩く

getでデータを送る
var frisby = require('frisby');                                                                                                    
frisby.create('getで送るテスト')
 .get('http://api.zipaddress.net/?zipcode=1000000')
postでデータを送る
var frisby = require('frisby');
frisby.create('postで送るテスト')
    .post('APIのURL',
    {                                    
        "param1": "hogehoge",
            "request_data": {
                "data_param1":"1"
            },
     }, { json:true })

{json:true}を忘れずに。

proxyを使う

GETの場合は.getの第二引数に、POSTの場合は.postの第三引数に設定すればいいっぽい。

frisby.create('GETの場合')
    .get('APIのURL', {'proxy':'http://xxxxxxxxxxxx:port/'})
var frisby = require('frisby');
frisby.create('POSTの場合')
    .post('APIのURL',
    {                                                                                                                                                   
        "param1": "hogehoge",
            "request_data": {
                "data_param1":"1"
            },
     }, { json:true,  'proxy':'http://xxxxxxxxxxxx:port/'})

APIのレスポンスを確認する

ヘッダー
var frisby = require('frisby');                                                                                                        
frisby.create('住所検索')
  .get('http://api.zipaddress.net/?zipcode=1000000')
  .expectStatus(200)
  .expectHeader('Content-Type', 'application/json; charset=utf-8')
.toss();

expectHeaderを使うと完全一致、expectHeaderContainsで部分一致、expectHeaderToMatchで正規表現が使えます。

レスポンスの中身

expectJSONを使います。

var frisby = require('frisby');
frisby.create('住所検索')
  .get('http://api.zipaddress.net/?zipcode=1000000')
  .expectStatus(200)
  .expectHeader('Content-Type', 'application/json; charset=utf-8')
  .expectJSON(
    {
            'code': 200, 
            'data': {
                        'address': '千代田区', 
                        'city': '千代田区', 
                        'fullAddress': '東京都千代田区', 
                        'pref': '東京都', 
                        'town': ''
                    } 
     }
  )
.toss();

expectJSONTypesでレスポンスの型の確認ができます。

var frisby = require('frisby');
frisby.create('住所検索')
  .get('http://api.zipaddress.net/?zipcode=1000000')
  .expectStatus(200)
  .expectHeader('Content-Type', 'application/json; charset=utf-8')
  .expectJSON(
    {
            'code': 200, 
            'data': {
                        'address': '千代田区', 
                        'city': '千代田区', 
                        'fullAddress': '東京都千代田区', 
                        'pref': '東京都', 
                        'town': ''
                    }
    }
  )
  .expectJSONTypes('data', {
      'address': String,
      'city': String,
      'fullAddress': String,
      'pref': String,
      'town': String
  })
.toss();

expectJSONTypesでは、期待するレスポンスをそのまま書かずに、第一引数に項目を指定する書き方をしています。expectJSONでも同じ書き方ができます。

レスポンスタイム

expectMaxResponseTimeでmsで指定します。

var frisby = require('frisby');
frisby.create('住所検索')
  .get('http://api.zipaddress.net/?zipcode=1000000')
  .expectStatus(200)
  .expectMaxResponseTime(1000)
.toss();

テストした内容を出力する

いろいろ出せる。

  • inspectJSON:リクエストをJSONで
  • inspectBody:リクエストをテキストで
  • inspectRequest:HTTPのリクエスト
  • inspectStatus:HTTPのステータスコード
  • inspectHeaders:HTTPのヘッダー

例えば、JSON形式のリクエストとHTTPのリクエストを出力してみるテストコードはこう。

var frisby = require('frisby');
frisby.create('住所検索')
  .get('http://api.zipaddress.net/?zipcode=1000000')
  .expectStatus(200)
  .expectHeader('Content-Type', 'application/json; charset=utf-8')
  .expectJSON(
    {
            'code': 200, 
            'data': {
                        'address': '千代田区', 
                        'city': '千代田区', 
                        'fullAddress': '東京都千代田区', 
                        'pref': '東京都', 
                        'town': ''
                    }
    }
  )
  .expectJSONTypes('data', {
      'address': String,
      'city': String,
      'fullAddress': String,
      'pref': String,
      'town': String
  })
  .expectMaxResponseTime(1000)
  .inspectJSON()
  .inspectRequest()
.toss();

このテストコードでテストを実行すると、このように出力してくれる。

% jasmine-node .
{ code: 200,
  data: 
   { pref: '東京都',
     address: '千代田区',
     city: '千代田区',
     town: '',
     fullAddress: '東京都千代田区' } }
{ json: false,
  uri: 'http://api.zipaddress.net/?zipcode=1000000',
  body: null,
  method: 'GET',
  headers: { 'content-type': 'application/x-www-form-urlencoded' },
  inspectOnFailure: false,
  baseUri: '',
  timeout: 5000 }

ちょっとハマったこと

frisbyは指定したリクエストを小文字に変換してしまうものがあるということ。

叩きたかったAPIで、大文字が入っている独自のheaderを設定する必要があったのですが、frisbyの小文字変換で送りたいヘッダーが送れず。Frisbyのプログラムを見ていたところ小文字変換の処理を見つけて原因が判明。

~/node_modules/frisby/lib/frisby.js

Frisby.prototype.addHeader = function(header, content) {
    this.current.request.headers[(header+"").toLowerCase()] = content+""; 
    return this;
}; 

仕方ないので、toLowerCase()を外しました。

少し幸せになれそう

frisbyを探すきっかけになったのは、新しく作るAPIのテストパターンが膨大になりそうで、1個1個テスト項目を手動ではやってられないと思ったから。
しかもどこかでテストがこけて、修正して再度テスト、なんてことを考えたら頭が痛くなったのもあった。

テスト目前にこのことに気づくと、テストパターン多い!でも効率な方法を探す時間もない!スケジュールも迫ってる!やるしかない!というパターンに陥るので、早めに気づいてよかったと思っています。

このfrisbyのおかげで、膨大なテストからだいぶ救われそう。