Rails + RSpec + FactoryBot + FakerでTestを書いてみた。

## はじめに

Railsで個人開発しているのですが、Testというものをあまり書いたことがなかったので、書いてみようと思い書きはじめてみたものの、思いの外まとまっている記事がなかったの備忘録ついでにまとめてみました。
(＊テスト初心者なので、間違い等あったらご指摘いただけると幸いです。)

## 環境
- macOS High Sierra 10.13.6
- Ruby 2.5.1
- Ruby on Rails 5.1.4
- gem
 - rspec-rails
 - factory_bot_rails
 - faker
 - devise 　＊ログイン周りをこれで実装しているためテスト実装前に設定が必要

## RSpecとは
Ruby/Ruby on Railsで作ったクラスやメソッドが正常に動作するかをテストするためのドメイン特化言語 (DSL)のこと。

## FactoryBotとは
テスト用のデータを作成してくれるツール。
もともとは、FactoryGirlという名前だったが、その名前に対して不快感を感じる人が多く、そのためFactoyBotという名前になったそう。

参考URL
[【翻訳】"Factory Girl"が"Factory Bot"に変わった理由（と移行手順）](https://qiita.com/jnchito/items/c71b8f66f61214227555)

## Fakerとは
様々なダミーデータを生成してくれるツール。

## 実際にやってみた

Relation

```
User has_many Groups
Group belongs_to User
```

### 事前準備
gemのインストール、RSpecのインストール

```ruby:Gemfile
group :development, :test do
  gem 'rspec-rails', '~> 3.7'
  gem 'factory_bot_rails'
  gem 'faker'
end
```

```
$ bundle install
```

RSpecのインストール

```
$ bundle exec rails generate rspec:install
```

FactoryBotの設定

```ruby:spec/rails_helper
RSpec.configure do |config|
  config.include FactoryBot::Syntax::Methods
end
```
上記は、以下のようにrspecのテストコード中でFactoryBotのメソッドを使用する際、
上述のコードを加えることで、以下のようにrspecのテストコード中でFactoryBotのメソッドを使用する際に、名前空間を指定する必要がなくなります。

```ruby
user = FactoryBot.create(:user)
↓
user = create(:user)
```
超便利。

ちなみに、


今回、Deviseを採用しているので、以下の設定も必要。
Deviseを使っている方は、こちらを参考に設定してみてください！

参考URL
[rspecのテスト環境でdeviseにログインする方法【rails】](https://qiita.com/Kohei_Kishimoto0214/items/e29e509b12a6eb484a42)



### テストデータの作成
```ruby:users.rb
FactoryBot.define do
  factory :user do
    password = Faker::Internet.password(8)
    nickname { Faker::Name.last_name }
    email { Faker::Internet.free_email }
    password { password }
    password_confirmation { password }
  end
end
```

```ruby:groups.rb
FactoryBot.define do
  factory :group do
    name { "名前" }
    host_name { "主催者の名前" }
    regulation { "参加条件" }
    explanation { "説明" }
    user
  end
end
```

```ruby:groups_controller_spec.rb
require 'rails_helper'

RSpec.describe Public::GroupsController, type: :controller do
  let!(:user) { create(:user) }
  let!(:group) { create(:group) }
  let!(:group_attributes) { attributes_for(:group) }

  describe 'GET #index' do
    before do
      login_user user
    end

    it "responds successfully" do
      get :index
      expect(response).to be_success
    end

    it "gets index" do
      get :index
      expect(response.status).to eq(200)
    end
  end

  describe 'GET #show' do
    before do
      login_user user
    end

    it "assigns the requested group to @group" do
      get :show, params: { id: group.id }
      expect(response.status).to eq(200)
    end
  end

  describe 'GET #new' do
    before do
      login_user user
    end

   it 'assigns new @group' do
     expect(response.status).to eq(200)
   end
 end

  describe 'Post #create' do
    before do
      login_user user
    end

    context "when @group can be saved" do
      it "saves group on DB" do
        expect do
          post :create, params: { group: group_attributes }
        end.to change(Group, :count).by(1)
      end
    end

    context "when @group can not be saved" do
      it "doesn't save group on DB" do
        expect do
          post :create, params: {
            group: {
              name: "",
              host_name: "サンプル",
              regulation: "サンプル",
              explanation: "サンプル"
            }
          }
        end.to change(Group, :count).by(0)
      end
    end
  end

  describe 'GET #edit' do
    before do
      login_user user
    end

    it "assigns the requested group to @group" do
      get :edit, params: { id: group.id }
      expect(response.status).to eq(200)
    end
  end

  describe 'PATCH #update' do
    before do
      login_user user
    end

    let(:update_attributes) do
      { regulation: 'アップデート', explanation: 'アップデート' }
    end

    it 'saves updated group' do
      expect do
        patch :update, params: { id: group.id, group: update_attributes }, session: {}
      end.to change(Group, :count).by(0)
    end

    it 'updates updated group' do
      patch :update, params: { id: group.id, group: update_attributes }
      group.reload
    end
  end

  describe 'DELETE #destroy' do
    before do
      login_user user
    end

    it 'deletes group' do
      expect do
        delete :destroy, params: { id: group.id }, session: {}
      end.to change(Group, :count).by(0)
    end
  end
end

```


### 解説
今回は、以下の定義済みのアクションのテストを実装

- index
- show
- new
- create
- edit
- destroy
- update

＊　resourcesで定義されるアクションすべてですね。


ソースコードを上から順に、

```ruby
let!(:user) { create(:user) }
let!(:group) { create(:group, user: user) }
let!(:group_attributes) { attributes_for(:group) }
```

letを使うことで、それぞれ変数を定義しています。
定義することで、それぞれexample内でテストデータを定義する必要がなくなり、コード量が減らせ、可読性も高まります。**letで定義された変数の状態は、他のテストに引き継がれないのがミソ！！**
上記はそれぞれテスト内で、

```
user
group
group_attributes
```
として使うことができます。

#### index

まずは、indexアクションから。
describeは、テストの説明が入ります。
以下のように使います。

```ruby
describe '説明' do
・・・
end
```

今後も出てくるので、必須です！

```ruby
before do
  login_user user
end
```

こちらは、describe内のテストが実行される前に、実行する処理になります。
（login_userは、[rspecのテスト環境でdeviseにログインする方法【rails】](https://qiita.com/Kohei_Kishimoto0214/items/e29e509b12a6eb484a42) で設定した、controller_macros.rbに定義されています。）

```ruby
it "responds successfully" do
  get :index
  expect(response).to be_success
end

it "get index" do
  get :index
  expect(response.status).to eq(200)
end
```

上は、indexに対し、getでアクセスしたときに、期待したresponseがbe_successであったかどうか、ということです。
下は、indexに対し、getでアクセスしたときに、正常なレスポンス（２００番）が返ってきているかをテストしています。

#### show
```ruby
describe 'GET #show' do
  before do
    login_user user
  end

  it "responds successfully" do
    get :show, params: { id: group.id }
    expect(response).to be_success
  end

   it "assigns the requested group to @group" do
     get :show, params: { id: group.id }
     expect(response.status).to eq(200)
   end
end
```

上は、showに対しgetでアクセスしたときに、期待したresponseがbe_successであったかどうか、ということです。
下は、showに対しgetでアクセスしたときに、正常なレスポンス（２００番）が返ってきているかをテストしています。

＊indexとの違いは、showアクションでは、id（ここでは、groupのid）が必要なので、paramsで設定する必要があります。



長くなってきたので、一旦ここまで。

次回は、newからスタート

**つづく**