devise+ウィザード形式でのユーザ登録のRspecテスト

##この記事で学べること
ウィザード形式の登録機能のsystemスペック
sessionメソッドを使用したときのcontrollerスペック

##概要
get,postアクションのcontrollerスペックについて

このテストに対するコードを解説した記事です
>[devise+ウィザード形式でのユーザ登録](https://qiita.com/tanegashiman/items/d1b370b22fe3a096f0d3)

##開発環境
MacOS Mojave 10.14.6
ruby 2.5.1
Rails 5.2.3
RSpec 3.9
  - rspec-core 3.9.0
  - rspec-expectations 3.9.0
  - rspec-mocks 3.9.0
  - rspec-rails 3.9.0
  - rspec-support 3.9.0
mysql Ver 14.14 Distrib 5.6.43, for osx10.14

```Gemfile
group :development, :test do
  gem 'factory_bot_rails'
  gem 'pry-byebug'
  gem 'rspec-rails'
  gem 'rails-controller-testing'
  gem 'spring-commands-rspec'
end

group :test do
  # Adds support for Capybara system testing and selenium driver
  gem 'capybara', '>= 2.15'
  gem 'webdrivers'
end
```

rspecの細かい設定は各自調べてください

##テストするコード
```signups_controller.rb
class SignupsController < ApplicationController
  def step1
    @user = User.new
  end

  def step1_validates  #step1で入力された情報のバリデーションチャックをするためのアクション
    # createアクションにデータを渡すためにsessionに代入
    session[:nickname] = user_params[:nickname]
    session[:email] = user_params[:email]
    session[:password] = user_params[:password_confirmation]
    session[:password_confirmation] = user_params[:password_confirmation]
    session[:lastname] = user_params[:lastname]
    session[:firstname] = user_params[:firstname]
    session[:lastname_kana] = user_params[:lastname_kana]
    session[:firstname_kana] = user_params[:firstname_kana]
    session[:birthday_year] = user_params[:birthday_year]
    session[:birthday_month] = user_params[:birthday_month]
    session[:birthday_day] = user_params[:birthday_day]

    # バリデーションチャックするためにインスタンスを作成
    @user = User.new(
      nickname: session[:nickname],
      email: session[:email],
      password: session[:password_confirmation],
      password_confirmation: session[:password_confirmation],
      lastname: session[:lastname],
      firstname: session[:firstname],
      lastname_kana: session[:lastname_kana],
      firstname_kana: session[:firstname_kana],
      birthday_year: session[:birthday_year],
      birthday_month: session[:birthday_month],
      birthday_day: session[:birthday_day]
    )
    #バリデーションチャック
    @user.valid?  #無効な値の場合はfalseとインスタンスに対してエラーメッセージを追加してくれる

    #今回はstep1でphonenumberを入力しないので設定しているpresence: trueに引っかかっている
    #このメソッドでphonenumberのエラー内容を削除してバリデーションを通過させる。メソッドはprivateにあります
    skip_phonenumber_validate(@user.errors) 

    # verify_recaptcha~~ はロボット確認の為のメソッドで今回は殆ど関係ないです。詳しくはgem 'recaptcha'で調べてください
    if verify_recaptcha(model: @user, message: "選択してください") && @user.errors.messages.blank? && @user.errors.details.blank?
      redirect_to step2_signups_path  #step1で入力したデータにバリデーションエラーがない場合はstep2に遷移する
    else
      @user.errors.messages[:birthday_day] = change_birthday_validate_message(@user) #エラーメッセージを変更するメソッドで今回は関係ないので説明は省略します
      render :step1
    end
  end

  def step2
    #入力フォームのためのインスタンスを作成
    @user = User.new
  end

  def create
    @user = User.new(
      #step1で入力したデータはsessionを使う
      nickname: session[:nickname],
      email: session[:email],
      password: session[:password_confirmation],
      password_confirmation: session[:password_confirmation],
      lastname: session[:lastname],
      firstname: session[:firstname],
      lastname_kana: session[:lastname_kana],
      firstname_kana: session[:firstname_kana],
      birthday_year: session[:birthday_year],
      birthday_month: session[:birthday_month],
      birthday_day: session[:birthday_day],
      #step2で入力したデータはparamsを使う
      phonenumber: user_params[:phonenumber]
    )
    if @user.save
      sign_in User.find(@user.id) unless user_signed_in?　#保存できたらログイン状態にする
      #sessionはもう使用しないので削除する
      session.delete(:nickname)
      session.delete(:email)
      session.delete(:password)
      session.delete(:password_confirmation)
      session.delete(:lastname)
      session.delete(:firstname)
      session.delete(:lastname_kana)
      session.delete(:firstname_kana)
      session.delete(:birthday_year)
      session.delete(:birthday_month)
      session.delete(:birthday_day)
      redirect_to new_address_path
    else
      render :step2
    end
  end

  private

  def user_params
    params.require(:user).permit(:nickname, :email, :password, :password_confirmation, :lastname, :firstname,
                                 :lastname_kana, :firstname_kana, :birthday_year,
                                 :birthday_month, :birthday_day, :phonenumber)
  end

  # 電話番号はstep1で入力しないのでバリデーションエラーがあったときにスキップさせるためのメソッド
  # step1で入力しないカラムのバリデーションエラーメッセージを削除する。
  def skip_phonenumber_validate(errors)
    errors.messages.delete(:phonenumber)  #stepの回数や入力するデータに合わせて変更してください
    errors.details.delete(:phonenumber)
  end

  # 生年月日のどれかにひとつでもバリデーションエラーがあった場合は同じエラーメッセージを表示する
  def change_birthday_validate_message(user)
    if user.errors.messages[:birthday_year].any? || user.errors.messages[:birthday_month].any? || user.errors.messages[:birthday_day].any?
      user.errors.messages.delete(:birthday_year)
      user.errors.messages.delete(:birthday_month)
      user.errors.messages[:birthday_year] = ["生年月日は正しく入力してください"]
      user.errors.messages[:birthday_year]
    end
  end
end
```
##Factory_Bot
```spec/factories/users.rb
FactoryBot.define do
  factory :user do
    nickname              { "tanegashiman" }
    email                 { "kkk@gmail.com" }
    password              { "123456a" }
    password_confirmation { "123456a" }
    lastname              { "向井" }
    firstname             { "治" }
    lastname_kana         { "ムカイ" }
    firstname_kana        { "オサム" }
    birthday_year         { 2018 }
    birthday_month        { 12 }
    birthday_day          { 31 }
    phonenumber           { "09000000000" }
  end
end
```
##コントローラースペック
細かいコントローラースペックのやり方は下記の記事を参考にしてください
>- [コントローラテストの基本を整理。](https://qiita.com/hyungg/items/53618cf093adbc4fbc35)
- [著書「RSpecによるRailsのテスト入門　テスト駆動開発の習得に向けた実践的アプローチ」著：Aaron Summer氏、訳：伊藤淳一氏、秋元利春氏、魚振江氏、2017年5月4日。](https://leanpub.com/everydayrailsrspec-jp)
- [Qiita「使えるRSpec入門・その1『RSpecの基本的な構文や便利な機能を理解する』」](https://qiita.com/jnchito/items/42193d066bd61c740612)
- [Qiita「使えるRSpec入門・その2『使用頻度の高いマッチャを使いこなす』」](https://qiita.com/jnchito/items/2e79a1abe7cd8214caa5)


```spec/controllers/signups_controller_spec.rb
require 'rails_helper'

RSpec.describe SignupsController, type: :controller do
  describe "get #step1" do
    it "レスポンスが成功すること" do
      get :step1
      expect(response).to be_successful
    end

    #200レスポンスは成功というステート
    it "200レスポンスを返すこと" do
      get :step1
      expect(response).to have_http_status "200"
    end
  end

  describe "#step1_validates" do
    context "有効なデータの場合" do 
      #　同じ処理はsubjectを使うことでｄｒｙにし同じデータを使っているとわかりやすくする
      subject {
        user_params = attributes_for(:user)
        #post　:アクション名, params: { パラメータ名: "値"}, session: { session名: "値" } でデータを指定できる
        post :step1_validates, params: { user: user_params},
                                session: {
                                  nickname: "tanegashiman",
                                  email: "kkk@gmail.com",
                                  password: "123456a",
                                  password_confirmation: "123456a",
                                  lastname: "向井",
                                  firstname: "治",
                                  lastname_kana: "ムカイ",
                                  firstname_kana: "オサム",
                                  birthday_year: 2018,
                                  birthday_month: 12,
                                  birthday_day: 31
                                }
      }
       #200レスポンスはリダイレクト成功というステート
      it "302レスポオンスを返すこと" do
        #設定したsubjectはこのように使う
        subject
        expect(response).to have_http_status "302"
      end

      it "電話番号確認ページにリダイレクトすること" do
        subject
        expect(response).to redirect_to step2_signups_path
      end
    end

    context "無効なデータの場合" do
      subject {
        #無効なデータのときは処理の流れを理解しているとスムーズに進む
        user_params = attributes_for(:user, nickname: "")
        post :step1_validates, params: { user: user_params},
                              session: {
                                nickname: '',
                                email: "kkk@gmail.com",
                                password: "123456a",
                                password_confirmation: "123456a",
                                lastname: "向井",
                                firstname: "治",
                                lastname_kana: "ムカイ",
                                firstname_kana: "オサム",
                                birthday_year: 2018,
                                birthday_month: 12,
                                birthday_day: 31
                              }
      }

      it "200レスポオンスを返すこと" do
        subject
        expect(response).to have_http_status "200"
      end
  
      it "step1にrenderすること" do
        subject
        expect(response).to render_template :step1
      end
    end
  end

  describe "get #step2" do
  before do
    # ここで定義しないとsessionがないと怒られる
    user_params = attributes_for(:user)
    session[:nickname] = user_params[:nickname]
    session[:email] = user_params[:email]
    session[:password] = user_params[:password_confirmation]
    session[:password_confirmation] = user_params[:password_confirmation]
    session[:lastname] = user_params[:lastname]
    session[:firstname] = user_params[:firstname]
    session[:lastname_kana] = user_params[:lastname_kana]
    session[:firstname_kana] = user_params[:firstname_kana]
    session[:birthday_year] = user_params[:birthday_year]
    session[:birthday_month] = user_params[:birthday_month]
    session[:birthday_day] = user_params[:birthday_day]
  end
    it "レスポンスが成功すること" do
      get :step2
      expect(response).to be_successful
    end

    it "200レスポンスを返すこと" do
      get :step2
      expect(response).to have_http_status "200"
    end
  end

  describe "create" do
    context "有効なデータの場合" do
      subject {
        user_params = attributes_for(:user)
        post :create, params: { user: user_params},
                      session: {
                        nickname: "tanegashiman",
                        email: "kkk@gmail.com",
                        password: "123456a",
                        password_confirmation: "123456a",
                        lastname: "向井",
                        firstname: "治",
                        lastname_kana: "ムカイ",
                        firstname_kana: "オサム",
                        birthday_year: 2018,
                        birthday_month: 12,
                        birthday_day: 31
                      }
      }
      it "302レスポンスを返すこと" do
        subject
        expect(response).to have_http_status "302"
      end

      it "送付先登録ページにリダイレクトされること" do
        subject
        expect(response).to redirect_to new_address_path
      end

      it "保存することができる" do
        user_params = attributes_for(:user)
        expect do
          subject
        end.to change { User.count }.by(1)
      end
    end

    context "無効なデータの場合" do
      subject {
        user_params = attributes_for(:user)
        post :create, params: { user: user_params},
                    session: {
                      nickname: "",
                      email: "kkk@gmail.com",
                      password: "123456a",
                      password_confirmation: "123456a",
                      lastname: "向井",
                      firstname: "治",
                      lastname_kana: "ムカイ",
                      firstname_kana: "オサム",
                      birthday_year: 2018,
                      birthday_month: 12,
                      birthday_day: 31
                    }
      }
      it "200レスポンスを返すこと" do
        subject
        expect(response).to have_http_status "200"
      end

      it "step2にrenderされること" do
        subject
        expect(response).to render_template :step2
      end

      it "保存することができない" do
        expect do
          subject
        end.to change { User.count }.by(0)
      end
    end
  end
end
```
難しいテストはしてないのでほとんど説明はないです。ぼくはsessionの渡し方と処理の流れを理解できてなくて結構時間がかかりました。

##システムスペック
基本的に[gem 'capybara'](https://github.com/teamcapybara/capybara)を使っています!!
細かいシステムスペックのやり方は下記の記事を参考にしてください
>- [使えるRSpec入門・その4「どんなブラウザ操作も自由自在！逆引きCapybara大辞典」](https://qiita.com/jnchito/items/607f956263c38a5fec24)
- [Everyday Rails - RSpecによるRailsテスト入門](https://leanpub.com/everydayrailsrspec-jp)

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

RSpec.describe 'Users', type: :system do
  scenario 'ユーザ登録することができる' do
    visit step1_signups_path
    expect(page).to have_content '会員情報入力'   #正しいページにアクセスできているか確認

    fill_in 'nickname', with: 'test' #inputタグに入力してる  fill_in 'タグのid', with: "入力したい値"
    fill_in 'email', with: 'kkk@gmail.com'
    fill_in 'password', with: '123456a'
    fill_in 'user_lastname', with: '向井'
    fill_in 'user_firstname', with: '治'
    fill_in 'user_lastname_kana', with: 'ムカイ'
    fill_in 'user_firstname_kana', with: 'オサム'
    select 1998, from: 'user_birthday_year' #selectタグを選択  select "値', form: "タグのid"
    select 12, from: 'user_birthday_month'
    select 31, from: 'user_birthday_day'
    click_button '次へ進む'

    expect(page).to have_content '電話番号の確認'    #ウィザード形式なので正しいページに遷移しているか確認
    expect do
      fill_in 'phonenumber', with: '0000000000'
      click_button '次へ進む'
    end.to change { User.count }.by(1)  #保存されているか確認
    expect(page).to have_content '住所入力' #保存後に正しいページに遷移しているか確認
  end
end
```
##最後に
今回の記事はあまり説明することがなかったですが、調べると様々マッチャがあったり、適度にDRYにするための機能があるのでもっと勉強しないといけないと感じました。
少しでも開発の手助けになると嬉しいです😆