Grape + RSpec + json_expressions で Awesome な API の受け入れテスト


Grape は Ruby で API を書くのに便利なフレームワークです。

Grape 自体については fakestarbaby 氏がすでにすてきなエントリを書いてくださっています。
[Grape | API生成マイクロフレームワーク #Rails #Gems #Ruby #grape #api_builders - Qiita](http://qiita.com/items/fa03cf333744b7c70e72)

ここではどうやってテストを書くのかということについて書いてみたいと思います。

## 想定

* RSpec の受け入れテストの request_spec を使うよ
* API は JSON を返すよ
* API 用のサブドメイン（api.foobar.com）を切っているよ
* JSON のテストは json_expressions を使うよ（[参考](http://qiita.com/items/72bf43a0d07321d072dd)）
* OAuth 2.0 の Provider になって Web Application Flow とかで認証しちゃったり
* モックは FactoryGirl で作っちゃうよ

## helper

まずは helper メソッドをいくつか api_helper.rb に定義します。

### include Rack::Test::Methods

Rack::Test::Methods をインクルードすることで、get, post 等々を事項した結果のテストをおこなえるようになります。

```ruby:spec/support/api_helper.rb
module ApiHelper
  include Rack::Test::Methods
end
```

### API 用のサブドメインのための設定

API 用のサブドメインを切ったのでそれに応じるように、ヘルパーメソッドを定義しておきます。

テストの場合、ドメイン名は example.com になるようなので、サブドメインは api.example.com にしています。さきほど api.foobar.com と書いたのはこのときの api.example.com と混同しないようにするためです。

```ruby:spec/support/api_helper.rb
module ApiHelper
  # (略)
  def domain
    'api.example.com'
  end

  def default_rack_env
    { 'HTTP_HOST' => domain }
  end
end
```


### shared_context で全体の前提を定義！

```ruby:spec/support/api_helper.rb
module ApiHelper
  # (略)
  shared_context 'api' do
    subject { eval("#{method}(url, parameters, rack_env)") }
    let(:parameters) { '' }
    let(:rack_env)   { default_rack_env }
  end
end
```

テスト用の request は HTTPメソッド名(url, parameters, rack_env) のようになるのですが、毎回書くのはかったるいので、subject に eval で押し込めました。

* [sinatraでテストの入門の入門(その1) - tomiの日記](http://tomiacannondale.hatenablog.com/entry/20110801/1312175754)
* [sinatraでテストの入門の入門(その2)sinatraでrspec - tomiの日記](http://tomiacannondale.hatenablog.com/entry/20110829/1314631619)

RSpec の subject を使うことで、it do end の中身は subject が実行された結果を受けるので、いきなり should から書き始めることができます。


### HTTP の status と body を一気に試す shared_examples_for を定義！

```ruby:spec/support/api_helper.rb
modulde ApiHelper
  # (略)
  shared_examples_for '200 Success' do
    its(:status) { should be(200) }
    its(:body)   { should match_json_expression(result) }
  end

  shared_examples_for '201 Created' do
    its(:status) { should be(201) }
    its(:body)   { should match_json_expression(result) }
  end

  shared_examples_for '204 No Content' do
    its(:status) { should be(204) }
    its(:body)   { should eq('') }
  end

  shared_examples_for '404 Not Found' do
    its(:status) { should be(404) }
    its(:body)   { should match_json_expression({ message: '404 Not Found' }) }
  end

  shared_examples_for '422 Unprocessable Entity' do
    its(:status) { should be(422) }
    its(:body)   { should match_json_expression({ message: '422 Unprocessable Entity' }) }
  end
end
```

先ほどの subject の結果で得られる last_response は status / body などのプロパティを持っているので、RSpec の its でさらに取り出してしまいます。

20x などの成功時の match_json_expression の中身は、使用時に let を使い result という名前で定義します。

* [すごいぞRSpec(attribute of subject即ちits編) - ぷろぐらまねが](http://d.hatena.ne.jp/yohfee/20110225/1298639902)


### OAuth 2 認証

OAuth 2 の認証が必要な場合は、先ほどの shared_context を拡張します。

```ruby:spec/support/api_helper.rb
module ApiHelper
  # (略)
  def oauth2_auth(token)
    header('Authorization', "Bearer #{token}")
  end

  shared_context 'api' do
    let(:user)                { FactoryGirl.create(:user) }
    let(:client_application)  { FactoryGirl.create(:client_application,
                                                   user: user) }
    let(:access_token)        { FactoryGirl.create(:access_token,
                                                   user: user,
                                                   client_application: client_application) }

    before(:each) { oauth2_auth(access_token.token) }

    subject { eval("#{method}(url, parameters, rack_env)") }
    let(:parameters) { '' }
    let(:rack_env)   { default_rack_env }
  end
end
```

## 実際の使い方

ここまで作った helper を使って、実際にテストを書いてみましょう。

```ruby:spec/requests/api/articles_spec.rb
describe Api do
  include ApiHelper
  include_context 'api'

  describe 'Articles' do
    let(:article) { FactoryGirl.create(:article, user: user) }

    describe 'GET /articles/:article_id' do
      let(:method) { 'get' }
      let(:url)    { "/articles/#{article.id}" }
      let(:result) do
        {
          id:      article.id,
          title:   article.title,
          content: article.content,
          user: {
            id:            user.id,
            name:          user.name,
            profile_image: user.profile_image.url(:small),
            created_at:    user.created_at.as_json,
            updated_at:    user.created_at.as_json,
          }
        }
      end

      it_behaves_like('200 Success')
    end

    describe 'DELETE /articles/:article_id' do
      let(:method) { 'delete' }
      let(:url)    { "/articles/#{article.id}" }

      it_behaves_like('204 No Content')

      it 'The article is successfully deleted.' do
        subject
        expect { Article.find(article.id) }.to raise_error ActiveRecord::RecordNotFound
      end
    end
  end
end
```


リクエスト時の parameter については必要があれば let は上書きができるので、上書きします。method や url は上の方で定義しておいて、context をネストさせれば、無駄なコードを書くことなくテストのコードの集中することが可能だと思います。

subject を it do の中で呼び出せば、response でない項目もテストが可能です。

## 全体として参考にしたもの

* [RSpec の入門とその一歩先へ - t-wadaの日記](http://d.hatena.ne.jp/t-wada/20100228/p1)