RSpecはここから始めよう

[Everyday Rails - RSpecによるRailsテスト入門](https://leanpub.com/everydayrailsrspec-jp)を読みました。
まずはここから始めようと思ったことをまとめます。

導入方法については以下の記事を参照
[RSpecを導入する](https://qiita.com/d0ne1s/items/1ecd114b33e80058215f)

##用語
**spec**：テストファイル
**example**：spec内に書かれているテスト1つ1つ


##specの種類
モデルスペック、コントローラースペック、フィーチャースペック、システムスペック、レスポンススペックの5種類があります。特に重要なのは以下の3つです。

**モデルスペック**：バリデーションや、メソッドの単体テスト。

**システムスペック**：統合テスト。ユーザーの動作を再現する。手動テストを自動でやる感じ。
昔はフィーチャースペックが使われていたけど、今はこっちが推奨されてるみたいです。
実行に時間がかかります。

**レスポンススペック**：主に外部APIとの通信をするテストで使われます。

##基本文法
まず`it '<テスト名>' do ~ end`のブロックの中にデータのセットアップや検証したい項目を書きます。

```rb
it 'can have many notes' do
  #検証したい内容が入る
end
```
次に`expect(<検証したい項目>).to <どうなっていて欲しいか>`を書きます。英語の文法と同じ語順なのでとても読みやすくて気に入っています。

```rb
it 'can have many notes' do
  project = ... #データのセットアップなど
  expect(project).to be_valid
end
```

以下のコマンドでテストを実行します。

```terminal
$ bin/rspec
.
.
Finished in 3.12 seconds (files took 0.93752 seconds to load)
1 examples, 0 failures
```

##テストデータの生成
各テスト内で`User.new`などで生成しても全然オッケーなのですが、めんどくさいので、`FactoryBot`というgemを使います。

```terminal
$ bin/rails g factory_bot:model user
```

```spec/factories/users.rb
FactoryBot.define do
  factory :user do
    name { 'Sample Tarou' }
    email { 'test@example.com' }
    password { 'mypassword1234' }
  end
end
```
あとはテスト内に`FactoryBot.create(:user)`と書くだけで、上記のユーザーのインスタンスを生成することができます。

```spec/models/user.rb
it 'is valid with a name, email, and password' do
  user = FactoryBot.create(:user)
  expect(user).to be_valid
end
```

##モデルスペック
```terminal
$ bin/rails g rspec:model user
```
```spec/models/user_spec.rb
require 'rails_helper'

RSpec.describe User, type: :model do
  #emailのバリデーションのテスト
  it { is_expected.to validate_presence_of :email }

  #フルネームを返すnameメソッドのテスト
  it "returns a user's full name as a string" do
    user = FactoryBot.build(:user, first_name: 'John', last_name: 'Doe')
    expect(user.name).to eq 'John Doe'
  end
end
```

##システムスペック
```terminal
$ bin/rails g rspec:system project
```

```spec/system/projects_spec.rb
require 'rails_helper'

RSpec.feature 'Projects', type: :system do
  it 'edits a project' do
    sign_in_as user
    visit root_path
    click_link 'Test Project 1'
    click_link 'Edit'
    fill_in_project_info('test_updated', 'hogefuga')
    click_button 'Update Project'
    expect(page).to have_content 'test_updated'
  end

  it 'creates a new project' do
    sign_in_as user
    expect do
      click_link 'New Project'
      fill_in_project_info('Test Project', 'Trying out Capybara')
      click_button 'Create Project'

      aggregate_failures do
        expect(page).to have_content 'Project was successfully created'
        expect(page).to have_content 'Test Project'
        expect(page).to have_content "Owner: #{user.name}"
      end
    end.to change(user.projects, :count).by(1)
  end
end
```

##リクエストスペック
```terminal
bin/rails g rspec:request home
```
```spec/requests/homes_spec.rb
require 'rails_helper'

RSpec.describe "Homes", type: :request do
  it 'responds successfully' do
    get root_path
    expect(response).to be_success
    expect(response).to have_http_status '200'
  end
end
```

##共通のテストデータ
`let`を利用することで、1つのファイル内で共通で使うテストデータを作成することができます。

```spec/models/user.rb
let(:user) { FactoryBot.create(:user) }

it 'is valid with a name, email, and password' do
  expect(user).to be_valid
end

it 'is Sample Tarou' do
  expect(user).to eq 'Sample Tarou'
end
```

上の例では`let`を使い`user`という変数の中にファクトリに定義したユーザーのインスタンスを格納しました。

##exampleをグループ分けする
`describe`、`context`を使って、exampleをグループ分けすることで、可読性をあげることができます。

```rb
describe 'search message for a term' do
  context 'when a match is found' do
    it 'returns notes that match the search term' do
      .
      .
    end
  end

  context 'when no match is found' do
    it 'returns an empty collenction when no result are found' do
      .
      .
    end

    it 'returns an empty collenction when serach conditions are invalid' do
      .
      .
    end
  end
end
```

##capybara
システムスペックを書く上で`capybara`というgemが必要です。
インストールすると、capybaraのDSL(独自の言語)が使えるようになります。
以下の`visit`、`click_link`、`fill_in`、`click_button`などがDSLです。非常に直感的で気に入っています。

```rb:specs/systems/projects_spec.rb
it 'signs in' do
  visit root_path
  click_link 'Sign In'
  fill_in 'Email', with: user.email
  fill_in 'Password', with: user.password
  click_button 'Log in'
  expect(page).to have_content 'ようこそ Sample Tarou 様'
end
```

##shoulda-matchers
`shoulda-matchers`というgemを使うと、macher(be_validとか)を拡張することができ、特にモデルスペックのバリデーションのチェックで力を発揮します。

```rb
it 'is invalid without a first name' do
  user = FactoryBot.build(:user, first_name: nil)
  user.valid?
  expect(user.errors[:first_name]).to include("can't be blank")
end
```
このバリデーションチェックのテストが

```rb
it { is_expected.to validate_presence_of :first_name }
```
このように1行で検証できるようになります。

デフォルトで使えるマッチャーについては以下の記事を参照
[使えるRSpec入門・その2「使用頻度の高いマッチャを使いこなす」](https://qiita.com/jnchito/items/2e79a1abe7cd8214caa5)

##メソッドを切り出す
複数箇所で共通する処理は、メソッドとして切り出すことができる。
ファイルの一番下に書くことが多い。

```rb
def go_to_project(name)
  visit root_path
  click_link name
end
```

複数のファイルで共通する処理は`specs/support`ディレクトリにファイルを作る。

```spec/support/login_support.rb
module LoginSupport
  def sign_in_as(user)
    visit root_path
    click_link "Sign in"
    fill_in "Email", with: user.email
    fill_in "Password", with: user.password
    click_button "Log in"
  end
end
```
```spec/rails_helper.rb
#以下のように記述することで、各ファイルでincludeする必要がなくなる
RSpec.configure do |config|
  config.include LoginSupport
end
```

明示的に読み込みたい場合には、各ファイル内で`include LoginSupport`のように記述する。

##終わりに
他にもたくさんTIPS的なものがありますが、全部いきなり全部意識するのは無理なので、まずはこの記事にまとめたテクニックでどんどんテストを書いていきます。

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