【Ruby on Rails】Rspec・rails-controller-testingを用いたテスト実施方法

ここではGemファイルのRspec、rails-controller-testingを用いた
テストの大まかな流れを記載しています。

前提
【開発環境】
・Rails7
・Docker（DockerファイルにてGemのbundle installが行われるように設定済）

目次
・テストで使用するGem(RSpecとrails-controller-testing)をインストール
・コントローラー&モデルのテストで使用するファイルを作成
・テスト用のDB作成（必要な場合）
・テストの実行（モデルの場合）
・テストの実行（コントローラーの場合）


<h1>【テストで使用するGem(RSpecとrails-controller-testing)をインストール】</h1>

**１ Gemfileを開きdevelopmentのテスト環境のブロックへrspec-railsを追記する。**

rspec-railsのバージョンが古いと、scopeエラーが発生することがあるので、できるだけ最新をとってくるようにする。（これで結構はまっていました…）

```ruby:Gemfile
group :development, :test do
  # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
  gem "rspec-rails", "~> 4.0.2"
  gem 'rails-controller-testing'
  gem "debug", platforms: %i[ mri mingw x64_mingw ]
end
```
**２ ターミナルでbuildコマンドを実行し、Gemをインストールする**

```ruby:terminal
docker-compose build
```
**３ rspecの設定ファイルを生成する**

```ruby:terminal
docker-compose run web bundle exec rails g rspec:install
```

specフォルダ配下に以下が生成されていれば良い
spec/spec_helper.rb
spec/rails_helper.rb

---------------------------------------------------

<h1>【コントローラー&モデルのテストで使用するファイルを作成】</h1>

**１ まずははじめにrailsのコンテナを起動する**

```ruby:terminal
# railsコンテナ立ち上げ
docker-compose up
```
```ruby:terminal
# 起動しているかどうかの確認
docker-compose ps
```
起動しているかどうかの確認時に、「State」がupになっていれば良い

**２ ユーザーモデルのテストを記載するファイルを作成する**
```ruby:terminal
docker-compose exec web bundle exec rails g rspec:model User
```
specフォルダ配下に以下が生成されていれば良い
spec/models/user_spec.rb

user_spec.rbのデフォルトの内容は以下
```ruby:user_spec.rb
    RSpec.discribe User, type: :model do
    # ペンディングはデフォルトで記載されている（削除してよい）
        pending "add some examples to (or delete) #{__FILE__}"
    end
```
**４ ユーザーコントローラーのテストを記載するファイルを作成する**
```ruby:terminal
    docker-compose exec web bundle exec rails g rspec:controller Users
```
specフォルダ配下に以下が生成されていれば良い
spec/controllers/users_controller_spec.rb

users_controller_spec.rbのデフォルトの内容は以下
```ruby:users_controller_spec.rb
    RSpec.describe UsersController, type: :controller do
    
    end
```
---------------------------------------------------
<h1>【テスト用のDB作成（必要な場合）】</h1>

**１ テスト用のDBを作成する**

DB作成コマンドを実行する

```ruby:terminal
docker-compose exec web bundle exec rails db:create
```
Railsでテストを実行する場合は、デフォルトでtestという環境で動作し、
DBもテスト用のものが使用される

<h1>【テストの実行（モデルの場合）】</h1>

```ruby:terminal
# モデルのテスト実行コマンド
docker-compose exec web bundle exec rspec ./spec/models/
```

```ruby:terminal（結果例）
# example：実行したテスト　failures：失敗したテスト　pending：保留しているテスト
1 example, 0 failures, 1 pending
```

<h3>【メソッドの定義（テスト駆動開発）】</h3>

**テスト駆動開発**
…一度間違ったメソッドを定義して、テスト実行する


**定義内容例**
まずはモデルで「年齢」を定義する
```ruby:app/models/user.rb
class User < ApplicationRecord
    def age
        0
    end
end
```

次にスペックファイルを記載する
```ruby:spec/models/user_spec.rb
    reqire 'rails_helper'
    
    RSpec.discribe User, type: :model do
    
        # 何をテストするのかのテストの対象を記載(一般的には#メソッド名)
        describe '#age' do
        
            # どういう条件下で行うテストであるかを書く
            context '5年前の生年月日の場合' do
            
                # 上記contextが表すユーザーを定義
                let(:user){User.new(birthday: Time.zone.now - 5.years) }
                
                # 最終的にどういう状況であるのが正しいのかをテストする
                it '年齢が５歳であること' do
                
                    # expectの中にはテスト対象とするメソッドの呼び出しなどを記載する
                    # 'eq'等の記載はマッチャーと呼ぶ（検索するときはexpect　マッチャーと記載する）
                    expect(user.age).to eq 5
                end
            end
        end
    end
    
    # ペンディングは削除する
    #   pending "add some examples to (or delete) #{__FILE__}"
    end
```

この状態でテスト実行する
```ruby:terminal
# モデルのテスト実行コマンド
docker-compose exec web bundle exec rspec ./spec/models/
```

app/models/user.rbで定義された値が0であり、
5ではないため以下のような結果が返ってくる

```ruby:terminal（結果例）
# example：実行したテスト　failures：失敗したテスト　pending：保留しているテスト
1 example, 1 failure
```

<h3>【メソッドの定義（正しく動作する場合）①】</h3>

まず生年月日から年齢を取得できるようにモデル側で定義する
```ruby:app/models/user.rb
class User < ApplicationRecord
    def age
        # 現在の日本時間
        now = Time.zone.now
        
        # 現在の日付と誕生日の日付をint型に直し、現在の日付 - 誕生日の日付を表す
        # 結果を10000で割ることで年数を取得している
        #（日付を引き算した年数の部分のみを取得して月以下の部分を切り捨てるために10000としている）→5桁目から上の部分を取得している
        (now.strftime('%Y%m%d')).to_i - birthday.strftime('%Y%m%d').to_i) / 10000
    end
end
```

テストの実行
```ruby:terminal
# モデルのテスト実行コマンド
docker-compose exec web bundle exec rspec ./spec/models/
```

```ruby:terminal（結果例）
# example：実行したテスト　failures：失敗したテスト　pending：保留しているテスト
1 example, 0 failure
```
<h3>【メソッドの定義（正しく動作する場合）②】</h3>

境界値で確認するためにモックを準備する

**モック**
…テスト用にダミーのオブジェクトを用意したり、
　特定のメソッドを選んだ場合に決まった値を返すように設定することができる。

 ```ruby:app/models/user.rb
 
    # berforeブロック内でtimezoneなどの挙動を設定することができる
    before do
      allow(Time.zone).to receive(:now).and_return(Time.zone.parse('2024/04/01'))
    end

    context '5年前の生年月日の場合' do
            
        # 上記contextが表すユーザーを定義
        let(:user){User.new(birthday: Time.zone.now - 5.years) }
                
        # 最終的にどういう状況であるのが正しいのかをテストする
        it '年齢が５歳であること' do    
        
            # expectの中にはテスト対象とするメソッドの呼び出しなどを記載する
            # 'eq'等の記載はマッチャーと呼ぶ（検索するときはexpect　マッチャーと記載する）
            expect(user.age).to eq 5
        end
    end
    
    context '5年前に生まれてちょうど誕生日の場合' do
      let(:user) {User.new(birthday: Time.zone.parse('2019/04/01')) }
      it '年齢が5歳であること' do
        expect(user.age).to eq 5
      end
    end
    context '5年前に生まれて誕生日が来ていない場合' do
      let(:user) {User.new(birthday: Time.zone.parse('2019/04/02')) }
      it '年齢が4歳であること' do
        expect(user.age).to eq 4
      end
    end
   
```

テストの実行
```ruby:terminal
# モデルのテスト実行コマンド（ドキュメント形式でテスト結果を表示できるオプション(-f)付き）
docker-compose exec web bundle exec rspec -f d ./spec/models/
```

テスト実施結果（例）
```ruby:terminal
User
  #age
    5年前の生年月日の場合
      年齢が5歳であること
    5年前に生まれてちょうど誕生日の場合
      年齢が5歳であること
    5年前に生まれて誕生日が来ていない場合
      年齢が4歳であること

Finished in 0.11773 seconds (files took 5.81 seconds to load)
3 examples, 0 failures
```

このようにモデルの動作が正しいかどうかの確認を行うことができる。

<h1>【テストの実行（コントローラーの場合）】</h1>

ジェネレーターコマンドで作成しておいたコントローラーに以下を記載する。
```ruby:spec\controllers\users_controller_spec.rb
require 'rails_helper'

RSpec.describe UsersController, type: :controller do

    describe 'GET #new' do
        # beforeブロックではこのコントローラーアクションにGETのHTTPメソッドで擬似的にアクセスしている
        # これによってGETのリクエスト結果がresponseという変数に格納される
        before { get :new }

        it 'レスポンスコードが200であること' do
            expect(response).to have_http_status(:ok)
        end

        it 'newテンプレートをレンダリングすること' do
            expect(response).to render_template :new
        end

        it '新しいuserオブジェクトがビューに渡されること' do
            expect(assigns(:user)).to be_a_new(User)
        end
    end
end
```

テストの実行
```ruby:terminal
# コントローラーのテスト実行コマンド（ドキュメント形式でテスト結果を表示できるオプション(-f)付き）
docker-compose exec web bundle exec rspec -f d ./spec/controllers/
```

テスト実施結果（例）
```ruby:terminal
UsersController
  GET #new
    レスポンスコードが200であること
    newテンプレートをレンダリングすること
    新しいuserオブジェクトがビューに渡されること

Finished in 3.02 seconds (files took 5.69 seconds to load)
3 examples, 0 failures
```

このようにコントローラーが正しく動作しているかどうかを確認することができる。