【rails】RSpecによる単体テスト（コントローラ）




### 準備

Gem``` rails-controller-testing ```をインストールする。

```ruby:Gemfile
group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  ...
  gem 'rails-controller-testing' #追記 
end
```

```console:terminal
$ bundle install
```

次のディレクトリを作成する。
　``` spec/controllers ```

次のファイルを作成する。
　```spec/controllers/tweets_controller_spec.rb```

```ruby:spec/controllers/tweets_controller_spec.rb
require 'rails_helper'

describe TweetsController do

end
```

specファイルを特定してテストを実行する。

```console:terminal
$ bundle exec rspec spec/controllers/tweets_controller_spec.rb

Finished in ~

0 examples, 0 failures
```
正常に実行できることを確認した。

# テストコード

### newアクション

```ruby:spec/controllers/tweets_controller_spec.rb
require 'rails_helper'

describe TweetsController do
  # describe 'http method' #action do
  describe 'GET' #new do
    it 'new.html.erbに遷移すること' do
      get :new
      expect(response).to render_template :new
    end
  end
end
```


##### response
example内で行われるリクエストの後の遷移先のビューの情報を持つインスタンス。

##### render_templateマッチャ
引数にシンボル型のアクションを指定することで、そのアクションがリクエストされた時に自動的に遷移するビューを返す。

テストを実行する。

```console:terminal
$ bundle exec rspec spec/controllers/tweets_controller_spec.rb

TweetsController
  GET #new
    new.html.erbに遷移すること

Finished in ~
1 example, 0 failures
```

### editアクション

コントローラ確認

```ruby:app/controllers/tweets_controller.rb
  def edit
    @tweet = Tweet.find(params[:id])
  end
```
editアクションでは、@tweetインスタンスを定義している。

```ruby:spec/controllers/tweets_controller_spec.rb
require 'rails_helper'

describe TweetsController do
  # describe 'http method' #action do
  describe 'GET' #new do
    ...

  describe 'GET #edit' do
    it "@tweetに正しい値が入っていること" do
      # factory_botで定義するインスタンスを一時的にDBに登録して取得する
      # 登録したデータはテスト完了時に削除される
      tweet = create(:tweet)
      # 作成したインスタンスのidをセットする
      get :edit, params: { id: tweet }
      #editアクションで取得するtweetが、テストで作成したtweetと同じであることを確認する
      expect(assigns(:tweet)).to eq tweet
    end

    it "edit.html.erbに遷移すること" do
      tweet = create(:tweet)
      get :edit, params: { id: tweet }
      expect(response).to render_template :edit
    end
  end
end
```

##### assignsメソッド
コントローラのテスト時に、アクションで定義するインスタンス変数をテストするためのメソッド。
直前で定義したアクションの中で定義されるインスタンス変数を、シンボル型でとる。
（@tweet -> :tweet）

factory_botでtweetインスタンスを定義する。

```ruby:spec/factories/tweets.rb
FactoryBot.define do
  factory :tweet do
    text {"hello!"}
    image {"hoge.png"}
    # created_atをランダムに生成
    created_at { Faker::Time.between(from: DateTime.now - 2, to: DateTime.now ) }
    user
  end
end
```
登録日カラムにて利用するFakerの説明は最後に。

### indexアクション

indexアクションを確認する。

```ruby:app/controllers/tweets_controller.rb
  ...
  def index
    @tweets = Tweet.includes(:user).order("created_at DESC").page(params[:page]).per(5)
  end
  ...
```

```ruby:spec/controllers/tweets_controller_spec.rb
describe 'GET #index' do
  it "@tweetに正しい値が入っていること"
    #tweetレコードを３つ保存して、変数tweetsにインスタンスに取得
    tweets = create_list(:tweet, 3)
    #indexアクションへの擬似的なリクエスト
    get :index
    # created_atを基準にして、降順にtweetsを並び替える処理
    expect(assigns(:tweets)).to match(tweets.sort{ |a, b| b.created_at <=> a.created_at })
  end
  it "index.html.erbに遷移すること"
    get :index
    expect(response).to render_template :index
  end
end
```

##### matchマッチャ
引数に配列クラスのインスタンスをとり、expectの引数と比較する。
配列の中身の順番までチェックする。



factory_botでのuserインスタンス定義する。

```ruby:spec/factories/users.rb
FactoryBot.define do
  factory :user do
    nickname              {"abe"}
    # email                 {"kkk@gmail.com"}
    password              {"00000000"}
    password_confirmation {"00000000"}
    sequence(:email) {Faker::Internet.email}
  end
end
```

##### Faker
今回、tweetインスタンスを複数作ることになるが、emailの値が重複するとバリデーションによりエラーが起こる。
そのため、Fakerで動的にダミーデータを生成する。

```ruby:Gemfile
group :test do
  ...
  gem 'faker', "~> 2.8"
end
```

```console:terminal
$ bundle install
```

### テストの実行

```console:terminal
$ bundle exec rspec spec/controllers/tweets_controller_spec.rb  
2020-05-16 16:26:37 WARN Selenium [DEPRECATION] Selenium::WebDriver::Chrome#driver_path= is deprecated. Use Selenium::WebDriver::Chrome::Service#driver_path= instead.

TweetsController
  GET #new
    new.html.erbに遷移すること
  GET #edit
    @tweetに正しい値が入っていること
    edit.html.erbに遷移すること
  GET #index
    @tweetに正しい値が入っていること
    index.html.erbに遷移にすること

Finished in 0.7887 seconds (files took 3.68 seconds to load)
5 examples, 0 failures
```

予期する通り動作することを確認できた。

##### RSpec参考書籍
https://leanpub.com/everydayrailsrspec-jp/read#leanpub-auto-section