RSpec 基本まとめ02(Feature,Request)

[RSpec 基本まとめ01(Model, FactoryBot, Controller)](https://qiita.com/mitsuooo/items/17cfd8877b61a711fc09)の続きです

# フューチャスペックでUIをテスト

- 統合テストと呼ばれる。これまでの単体テストは違って、開発したソフトウェア全体がシステムとして動作するか検証
- Rails5.1ではシステムテストという名前でデフォルトでサポートされるようになったので、システムスペックへ移行することがお勧めされている（内容的にはほぼ同じ）

## Capybara gem インストール

ブラウザの操作をシミュレートするために、Capybara gemを使用する。主に、リンクのクリック、フォームの入力、画面の表示を検証することができる

(Rails5.1からはCapybaraは既にインストールされているが、補足として記載)

```Gemfile
group :test do
  # selenium_chromeがデフォルト登録されたのがv2.15以降のためバージョンを指定
  gem 'capybara', '~> 2.15.2'
end
```

また、CapybaraはRails開発環境で実行可能なジェネレータは用意されていないため、テスト環境にだけ追加することで、開発環境のメモリ消費を軽くすることができる。

*また、bundleコマンド後、Capybaraを読み込むよう設定*

```spec/rails_helper.rb
.
.
require 'spec_helper'
require 'rspec/rails'

# 追加
require 'capybara/rspec'
.
```

## ファイル生成

```
# 例
rails g rspec:feature posts
```

## テスト例

```spec/features/posts_spec.rb
require 'rails_helper'

RSpec.feature "Posts", type: :feature do
  # 例：ユーザーがポストする
  
  scenario "user creates a new project" do
    user = FactoryBot.create(:user)

    visit root_path
    click_link "Sign in"
    fill_in "Password", with: user.password
    click_button "Log in"


    expect {
      click_link "New Post"
      fill_in "Name", with: "Test Post"
      click_button "Create Post"

      expect(page).to have_content "Post was successfully created"
      expect(page).to have_content "Test Post"
      expect(page).to have_content "Owner: #{user.name}"
    }.to change(user.posts, :count).by(1)
  end
end
```

- コントローラスペックとフューチャスペックの違いは、利用者と同じようにフォーム等を使って、画面操作でのテスト。コントローラスペックはUIを無視して、パラメータを直接コントローラのメソッドに送信する

- また、フューチャスペックでは1つのexample、もしくは1つのシナリオで複数のエクスペクテーションを書くのは問題ではない。

- `scenario` : `it`と同様に、`example`の起点を表す。
- `visit` : ページを開く
- `click_link` : リンクを押す
- `fill_in ~~, with --` : フォームを入力。withの後に値を設定できる。
- `click_button` : ボタンをクリック。（**起動されたアクションが完了する前に、次の処理へうつってしまうことがあるため、1個以上のエクスペクテーションを実行し、処理の完了を待つ**）

- `page` : ページの要素を取得
- `have_content` : 想定した文字列がページ内にあるか検証

他にも、よく使用されると考えられるのが、
- `check` : チェックボックスのラベルをチェック
- `uncheck` : チェックボックスのラベルを外す
- `choose` : ラジオボタンのラベルを選択する
- `select ~~, from: --` : セレクトメニューからオプションを選択
- `attach_file` : ファイルアップロードのラベルでファイル添付
- `have_css` : 指定したcssに一致する要素があるか検証
- `have_selector` : 指定したセレクタに一致する要素があるか検証
- `have_current_path` : 現在のパスが指定したパスであるか検証

[Capybara DSL ドキュメント](https://github.com/teamcapybara/capybara#the-dsl)で、他のメソッドも多く掲載されている。

## フューチャスペックをデバッグ

Capybaraはヘッドレスブラウザ（UIを持たない）を使ってテストを実行するため、処理ステップを目で確認することができない。
ただ、`save_and_open_page`メソッドを、テストが失敗する直前の箇所に記述することで、Railsがブラウザに返したHTMLを確認することができる

```
# ログインしていないことによるエラーを確認
scenario "guest adds a post" do
  visit posts_path
  save_and_open_page
  # ログインしていないことで、別ページに遷移しており、該当リンクがないことでエラー
  click_link "New Post"
end
```

こうすると、エラー内容の中に、ファイルのパスが表示され、そこからブラウザ上で確認することが出来る。（ログインしてないから、別のページにリダイレクトしており、ボタンがなかった！などが判明する）

また、`Launchy` gem をインストールすることで、自動でブラウザに表示されるようになる。

`save_and_open_page`メソッドは、デバッグ用のメソッドのため、不要になったら削除するのを忘れないように！

### JavaScript込みの操作をテスト

この節の内容が、自分の開発環境ではまだ正常に動作していないため、後ほど更新します！💦

# リクエストスペック

現在、コントローラスペックではなく、リクエストスペックが推奨されている。そのため、フィーチャスペックで使用した、Capybaraは使用しません。それは、ブラウザの操作を検証する訳ではないからです。代わりに、HTTPメソッド(GET, POST, DELETE, PATCH)を使用します。
ここでは、APIのテストに関して記述します。

## ファイル作成

```
# 例
bin/rails g rspec:request posts_api
```

このコマンドで、`spec/requests/posts_apis_spec.rb`ファイルが生成されるが、`API`が複数形になり不自然なため、手動で変更します。

```
⇨ spec/requests/posts_api_spec.rb
```

## テスト例

### GETリクエスト

- 記述に関しては、コントローラスペックとほぼ変わりませんが、リクエストスペックは出来ることが増えます。

```spec/requests/posts_api_spec.rb
require 'rails_helper'

RSpec.describe "Posts Api", type: :request do
  it "loads a post" do
    user = FactoryBot.create(:user)
    FactoryBot.create(:post,
      name: "Sample Post"
    )
    FactoryBot.create(:post,
      name: "Second Sample Post",
      owner: user
    )

    get api_posts_path, params: {
      # APIではユーザーのメアドとサインインするためのトークンが必要
      user_email: user.email,
      user_token: user.authentication_token
    }

    expect(response).to have_http_status(:success)
    json = Json.parse(response.body)
    expect(json.length).to eq 1
    post_id = json[0]["id"]
 
    # ownerを設定したpostのみ読み込まれるか検証
    get api_post_path(post_id), params: {
      user_email: user.email,
      user_token: user.authentication_token
    }

    expect(response).to have_http_status(:success)
    json = JSON.parse(response.body)
    expect(json["name"]).to eq "Second Sample Post"

  end
end
```

- コントローラスペックよりも、フューチャスペックぽさがある記述になります。
- コントローラスペックとは異なり、好きなルーティング名を何度でも使用することが出来る。リクエストスペックはコントローラに結びつかないため。そのため、テストしたいルーティング名をしっかり指定しているか確認することが必要。

### POSTリクエスト

```spec/requests/posts_api_spec.rb
require 'rails_helper'

describe "Posts API", type: :request do
  .
  .
  
  it "creates a post" do
    user = FactoryBot.create(:user)

    post_attributes = FactoryBot.attributes_for(:post)

    # postリクエストを送り、postが1件作成されることを検証
    expect {
      post api_posts_path, params: {
        user_email: user.email,
        user_token: user.authentication_token,
        # プロジェクトの属性も含める
        post: post_attributes
      }
    }.to change(user.posts, :count).by(1)

    expect(response).to have_http_status(:success)
  end
end
```

### コントローラスペックをリクエストスペックに置き換え

リクエストスペックが推奨されているので置き換えてみます。

```spec/requests/posts_spec.rb
.
before do
  @user = FactoryBot.create(:user)
end

# 有効な属性値を渡し、post作成を検証
it "adds a post" do
  post_params = FactoryBot.attributes_for(:post)
  sign_in @user
  expect {
    post posts_path, params: { post: post_params }
  }.to change(@user.posts, :count).by(1)
end

# 無効な属性値を渡し、postが作成されないことを検証
it "does not add a post" do
  post_params = FactoryBot.create(:post, :invalid)
  sign_in @user
  expect {
    post posts_path, params: { post: post_params }
  }.to_not change(@user.posts, :count)
end
```

- コントローラスペックとの違いは、Postコントローラのcreateアクションに直接依存するのではなく、具体的なルーティング名を指定してPOSTリクエストを送信する点。

- また、Deviseの`sign_in`ヘルパーを使用するには、下記のように、リクエストスペックで使用することを明記します。

まず`spec/support/request_spec_helper.rb`を作成

```spec/support/request_spec_helper.rb
module RequestSpecHelper
  include Warden::Test::Helpers

  def self.included(base)
    base.before(:each) { Warden.test_mode! }
    base.after(:each) { Warden.test_reset! }
  end

  def sign_in(resource)
    login_as(resource, scope: warden_scope(resource))
  end

  def sign_out(resource)
    logout(warden_scope(resource))
  end

  private

  def warden_scope(resource)
    resource.class.name.underscore.to_sym
  end
end
```

```spec/rails_helper.rb
.
.
RSpec.configure do |config|
  .
  .
  config.include Devise::Test::ControllerHelpers, type: :controller
  # 追加
  config.include RequestSpecHelper, type: :request
end
```

## 参考
- [Everyday Rails - RSpecによるRailsテスト入門](https://leanpub.com/everydayrailsrspec-jp)

- https://teratail.com/questions/217153

- [Capybara DSL ドキュメント](https://github.com/teamcapybara/capybara#the-dsl)