【RSpec】shared_contextを使ってレスポンスチェックを共通化する

# 動作検証環境
- Rails 5.2.3
- rspec-rails 3.8.2

# shared_context 作成
- ポイント
  - テスト本体で`subject (ここでは send_request)`を使うため、`expect`の前に`send_request`を呼ぶ。
  - 順序が不定な場合は、[json_expressions](https://github.com/chancancode/json_expressions) の`match_json_expression`を使う。

```ruby:spec/support/shared_context_for_api.rb
RSpec.shared_context 'api' do
  shared_examples 'http_status_code_200' do
    it 'returns 200' do
      send_request

      expect(response).to be_successful
      expect(response.status).to eq(200)
      # または以下。お好みで。
      # expect(response).to have_http_status(:ok)
    end
  end

  shared_examples 'http_status_code_200_with_json' do
    it 'returns 200 with JSON' do
      send_request

      expect(response).to be_successful
      expect(response.status).to eq(200)
      expect(JSON.parse(response.body).deep_symbolize_keys).to eq(expected_response)
    end
  end

  shared_examples 'http_status_code_200_with_json_expression' do
    it 'returns 200 with JSON' do
      send_request

      expect(response).to be_successful
      expect(response.status).to eq(200)
      expect(response.body).to match_json_expression(expected_response)
    end
  end

  shared_examples 'http_status_code_404' do
    it 'returns 404' do
      send_request

      expect(response).to_not be_successful
      expect(response.status).to eq(404)
      # または以下。お好みで。
      # expect(response).to have_http_status(:not_found)
    end
  end

  shared_examples 'http_status_code_500' do
    it 'returns 500' do
      send_request

      expect(response).to_not be_successful
      expect(response.status).to eq(500)
    end
  end
end
```

# shared_context 利用
- ポイント
  - `include_context 'api'`
  - `it_behaves_like 'http_status_code_200'` （`'http_status_code_200'` に ↑の`shared_examples` で定義したものを指定する）

```ruby:spec/requests/users_spec.rb
RSpec.describe Api::V1::UsersController, type: :request do
  include_context 'api'

  describe 'GET /api/v1/users' do
    context 'without params' do
      subject(:send_request) { get '/api/v1/users' }

      let(:expected_response) { ... }

      before { create_list(:user, 2) }

      it_behaves_like 'http_status_code_200_with_json'
    end
  end

  describe 'GET /api/v1/users/:email' do
    context 'when the user exists' do
      subject(:send_request) { get "/api/v1/users/#{@user.id}" }

      let(:expected_response) { ... }

      before { @user = create(:user) }

      it_behaves_like 'http_status_code_200_with_json'
    end

    context 'when the user does not exist' do
      subject(:send_request) { get '/api/v1/users/non_existing_user_id' }

      it_behaves_like 'http_status_code_404'
    end
  end
end
```

## `expected_response`としてJSONを使うパターン

```ruby
  describe 'GET /api/v1/endpoint' do
    context 'with valid request' do
      subject(:send_request) { get '/api/v1/endpoint' }

      let(:expected_response) do
        JSON.parse(file_fixture('endpoint_response_index.json').read, symbolize_names: true)
      end

      it_behaves_like 'http_status_code_200_with_json_expression'
    end
  end
```

## `subject`を使わず`before`だけで書く
- この場合、`shared_context`の先頭で、毎回`send_request (= subject)`を呼ぶ必要はない。
 
```ruby
RSpec.describe Api::V1::UsersController, type: :request do
  include_context 'api'

  describe 'GET /api/v1/users' do
    context 'without params' do
      let(:expected_response) { ... }

      before do
        create_list(:user, 2)
        get '/api/v1/users'
      end

      it_behaves_like 'http_status_code_200_with_json'
    end
  end
end
```