Rails: capybara_discoballで作るスタブ不要のAPIモックサーバ

この記事では、Railsアプリ内（開発環境・テスト環境）で使える、スタブ不要のAPIモックサーバの実装方法を整理します。

利用する gem は [capybara_discoball](https://github.com/thoughtbot/capybara_discoball) です。テストのフレームワークはRSpecを使います。

# 環境

* ruby 2.4.5
* rails 5.2.2
* rspec-rails 3.6.1

# 実装例

開発環境・テスト環境両方で使えるAPIモックサーバを [capybara_discoball](https://github.com/thoughtbot/capybara_discoball) を使って実装します。

## gem のインストール

まず、必要な gem をインストールします。

```ruby:Gemfile
group :development, :test do
  # 省略
  gem 'sinatra'
  gem 'thin'
  gem 'capybara_discoball'
end
```

```
bundle install
```

## APIモックサーバ

APIモックサーバは、アプリとは直接関係のない開発・テスト用の機能なので、`lib` 配下に書きます。（テスト環境でのみ利用する場合は、`spec/support` 配下に書きます。）

### fake_api/application.rb

`Sinatra::Base` でAPIモックサーバアプリを作ります。例えば次のように、`GET /contracts` で任意のJSONレスポンスを返します。

```ruby:lib/fake_api/application.rb
require 'sinatra'

module FakeApi
  class Application < Sinatra::Base
    get '/contracts' do
      json_response 200, 'contracts.json'
    end

    private

    def json_response(response_code, file_name)
      content_type :json
      status response_code
      File.open(Rails.root.to_s + '/lib/fixtures/fake_api/' + file_name, 'rb').read if file_name
    end
  end
end
```

### fixtures/fake_api/json

上の `json_response` メソッドに書いた通り、`fixtures/fake_api` 配下のJSONファイルをレスポンスボディに使います。

```json:lib/fixtures/fake_api/contracts.json
{
  "status": "ok",
  "result": [
    {
      "id": 1,
      "name": "東京太郎"
    }, 
    {
      "id": 2,
      "name": "大阪花子"
    }
  ]
}
```

## APIモックサーバの起動

開発環境・テスト環境でRailsアプリを起動する時に、APIモックサーバも起動させます。その設定は `config/initializers` 配下に書きます。

```ruby:config/initializers/fake_api_runner.rb
if Rails.env.development? || Rails.env.test?
  require 'capybara_discoball'
  require Rails.root.to_s + '/lib/fake_api/application.rb'

  Capybara::Discoball.spin(::FakeApi::Application) do |server|
    FAKE_API_BASE_URL = "#{server.host}:#{server.port}".freeze
  end
end
```

## Railsアプリ内でのコール

Railsアプリ内から `http://FAKE_API_BASE_URL` で起動中のAPIモックサーバにアクセスできます。

実際のAPI呼び出しコードは、次のようになります。

```ruby:clients/some_api/client.rb
module SomeApi
  class Client
    BASE_URL = 'https:/some_api.com'.freeze
    TEST_BASE_URL = 'https://test.some_api.com'.freeze

    cattr_accessor :base_url

    self.base_url =
      if Rails.env.production?
        BASE_URL
      elsif Rails.env.staging?
        TEST_BASE_URL
      else
        # config/initializers/fake_api_runner.rb
        FAKE_API_BASE_URL
      end
    
    # 処理省略 

    private

    def url
      if actual_api_env?
        base_url + self.class::ENDPOINT
      else
        'http://' + base_url + self.class::ENDPOINT
      end
    end

    def actual_api_env?
      Rails.env.production? || Rails.env.staging?
    end
  end
end
```

これで開発環境・テスト環境ともに、`SomeApi::Client.base_url` でAPIモックサーバのルートパスを呼び出せます。

試しに `rails console` で確認してみます。

```
rails c
[1] pry(main)> ::SomeApi::Client.base_url
=> "127.0.0.1:53732"
```

ブラウザで `127.0.0.1:53732` を叩くと、`contracts.json` の内容が表示されます。

# References

* [Faking APIs in Development and Staging](https://thoughtbot.com/blog/faking-apis-in-development-and-staging)
* [capybara_discoball](https://github.com/thoughtbot/capybara_discoball)
* [Rails開発でWebMockを使ってAPIアクセスをスタブ化する](https://dev.classmethod.jp/server-side/rails-development-with-webmock/)