0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

ウィザード形式でユーザ登録のフォームを作成してみた④ 〜単体テスト〜

Last updated at Posted at 2020-06-12

【この記事の説明】

現在、私はプログラミングスクールでプログラミング(主にRuby)の勉強をしています。
そして、その一環でチーム開発(メルカリもどきのサイト作成)を行っており、私はその中で記事のタイトルにあるように、ウィザード形式でユーザの登録ができるようにする作業を担当致しました。
又、スクールからの指示で、この登録フォームにはdeviseを使用するようにとのことでした。

この記事は、その担当作業についてまとめたものになります。

【作業】

ここから私が行った作業(そこで困ったこと)を記事として記載していこうと思います
※この記事では、単体テストについて記載したいと思います

単体テスト

ファイルの内のクラス一つに対して行うテスト

又、テストは一つの処理に対して、問題なく処理するかどうかを確認するのが良いとされています
(例)
データがセーブされるか/飛んで欲しいページに飛ぶか→◯
データがセーブされて、その上でページに飛ぶか→×

テストのためのgem

railsでテストをするには、RSpecを利用します
このRSpecを利用するには、gem 'rspec-rails'をインストールすることが必要です
又、specファイルの記述効率化を図るため、factory_botを利用します
factory_botはgem 'factory_bot_rails'をインストールすることで利用できます

課題ではモデルの単体テストをするだけで良かったのですが、コントローラテストも少しやってしまった私はそれ用のgemである、gem 'rails-controller-testing'もインストールしました
又、コントローラでは、データ保存をするので、保存したテスト用データを削除するgem 'database_cleaner'もインストールしました
ただ、最新のRSpecによるとこのgemは非推奨とのことでした

/Gemfile
group :development, :test do
  gem 'rspec-rails'
  gem 'factory_bot_rails'
  gem 'rails-controller-testing'
  gem 'database_cleaner'
end

テストについて

テストはどうして行うのか
 => プログラムが意図したように動くか確かめる為

RSpec

RSpecとは、Rubyを使って作成されたテスト用言語

RSpec設定

上記に書きましたが、Gemfileにgem 'rspec-rails'を記述します
この時に、gem 'web-console'がtest環境にないことを確認します
※もし、test環境にあると不具合が生じるので、その場合はdevelopment環境にのみあるようにしましょう

/Gemfile
group :development, :test do
  gem 'rspec-rails'
end
group :development do
  gem 'web-console'
end

ターミナルで、bundle installします

ターミナル
% bundle install

RSpec用の設定ファイルを作成する為に、下記コマンドを行います

ターミナル
% rails g rspec:install

下記のファイルが作成されます

ターミナル
      create  .rspec
      create  spec
      create  spec/spec_helper.rb
      create  spec/rails_helper.rb

rails_helper.rb
RailsでRSpecを使用する場合、共通の設定を記載しておくファイル
テストを実行するファイルに、このファイルを読み込ませることで共通設定やメソッドを使用することができる
spec_helper.rb
ほぼ、rails_helper.rbと同じ用途のファイルですが、RailsではないフレームワークでRSpecを使用する場合に使用します

.rspec
このファイルに--format documentationを記述します

.rspec
--require spec_helper
--format documentation

フォルダ・ファイル作成
今回私がテストしたのはモデルとコントローラなので、specフォルダの中に、そのフォルダを作成します
そして作成したフォルダの中にテスト用のファイルを作成します

私が作成したモデルは3つあるので、3つテスト用のファイルを作成します
又、factory_botを使用するので、それ用のフォルダ、ファイルが必要な為、それも作成します

specフォルダ
 => models(フォルダ)
  => user_spec.rb(/spec/models/user_spec.rb)
  => profileaddress_spec.rb(/spec/models/profileaddress_spec.rb)
  => deliveryaddress_spec.rb(/spec/models/deliveryaddress_spec.rb)
 => controller(フォルダ)
  => registrations_controller_spec.rb(/spec/controllers/registrations_controller_spec.rb)
 => factories(フォルダ)
  => user.rb(/spec/factories/user.rb)(model用)
  => profileaddress.rb(/spec/factories/profileaddress.rb)(model用)
  => deliveryaddress.rb(/spec/factories/deliveryaddress.rb)(model用)
  => registrations.rb(/spec/factories/registrations.rb)(controller用)
 => rails_helper.rb(/spec/rails_helper.rb)
 => spec_helper.rb(/spec/spec_helper.rb)

factory_bot

factory_botとは、簡単にダミーのインスタンスを作成することができるgemです
どのように使用するかはmodelテストの部分で記載します

modelテスト

モデルのテストでは、バリデーションがきちんとかかっているかテストで確認をしました

モデルは、3つですが、この記事ではuserモデルのみ記載したいと思います

/spec/models/user_spec.rb
require 'rails_helper'
require 'spec_helper'
require 'devise'

describe User do
  context 'ウィザード形式1ページ目(必須事項は埋まっていない時にエラー表示をするか)'do
    it "必須項目が間違いなく入力されていれば進める"do
      user = build(:user) # ←の:userは下記に記載した(/spec/factories/user.rb)のfactory :user do 〜 endを意味しています
      expect(user).to be_valid
    end
    
    it "ニックネームがないと進めない"do
      user = build(:user, nickname: nil) # ←の(:user, nickname: nil) はfactory :user do 〜 endでnicknameがnilの状態であるということを意味しています
      user.valid?
      expect(user.errors[:nickname]).to include("を入力してください")
    end

    it "メールアドレスがない場合は進めない" do
      user = build(:user,email: nil)
      user.valid?
      expect(user.errors[:email]).to include("を入力してください")
    end
   # :userの中のemailがnilのものをuserに代入した場合、userにはバリデーションがかかってエラーになるか
   # かかったエラーには"を入力してください"が含まれているか

    it "パスワードがない場合は進めない" do
      user = build(:user,password: nil)
      user.valid?
      expect(user.errors[:password]).to include("を入力してください")
    end

    it "パスワードは2回入力しない場合は進めない" do
      user = build(:user,password_confirmation: "")
      user.valid?
      expect(user.errors[:password_confirmation]).to include("とパスワードの入力が一致しません")
    end

    it "名字が入力されていないと進めない" do
      user = build(:user,family_name: nil)
      user.valid?
      expect(user.errors[:family_name]).to include("を入力してください")
    end

    it "名前が入力されていないと進めない" do
      user = build(:user,given_name: nil)
      user.valid?
      expect(user.errors[:given_name]).to include("を入力してください")
    end

    it "名字(カナ) が入力されていないと進めない" do
      user = build(:user,family_name_kana: nil)
      user.valid?
      expect(user.errors[:family_name_kana]).to include("を入力してください")
    end

    it "名前(カナ)が入力されていないと進めない" do
      user = build(:user,given_name_kana: nil)
      user.valid?
      expect(user.errors[:given_name_kana]).to include("を入力してください")
    end

    it "生年月日が入力されていないと進めない" do
      user = build(:user,birthday: nil)
      user.valid?
      expect(user.errors[:birthday]).to include("を入力してください")
    end
    
  end


  context 'ウィザード形式1ページ目(誤入力時のエラー表示がされるか)'do
    it "必須項目が間違いなく入力されていれば進める"do
      user = build(:user)
      expect(user).to be_valid
    end

    it "メールアドレスが誤入力された(正規表現とあっていない)場合は進めない:devise" do
      user = build(:user,email: %w[user@foo..com user_at_foo,org example.user@foo.
        foo@bar_baz.com foo@bar+baz.com])
      user.valid?
      expect(user.errors[:email]).to include("は不正な値です")
    end
    it "メールアドレスが誤入力された(正規表現とあっていない)場合は進めない:自作メソッド" do
      user = build(:user,email: %w[user@foo..com user_at_foo,org example.user@foo.
        foo@bar_baz.com foo@bar+baz.com])
      user.valid?
      expect(user.valid_email?).to eq(nil)
    end

    it "パスワードが7文字以上でないと進めない" do
      user = build(:user,password: "aaa1",password_confirmation: "aaa1")
      user.valid?
      expect(user.errors[:password]).to include("は7文字以上で入力してください")
    end
    it "パスワードが英字1文字、数字1文字以上入っていないと進めない" do
      user = build(:user,password: "aaaaaaa",password_confirmation: "aaaaaaa")
      user.valid?
      expect(user.password_complexity).to include("は英字と数字をそれぞれ1文字以上含める必要があります")
    end

    it "名字が全角で入力されていないと進めない(アルファベットver)" do
      user = build(:user,family_name: "yamada")
      user.valid?
      expect(user.errors[:family_name]).to include("は全角で入力してください")
    end
    it "名字が全角で入力されていないと進めない(半角カタカナver)" do
      user = build(:user,family_name: "ヤマダ")
      user.valid?
      expect(user.errors[:family_name]).to include("は全角で入力してください")
    end


    it "名前が全角入力されていないと進めない(アルファベットver)" do
      user = build(:user,given_name: "tarou")
      user.valid?
      expect(user.errors[:given_name]).to include("は全角で入力してください")
    end
    it "名前が全角入力されていないと進めない(半角カタカナver)" do
      user = build(:user,given_name:"タロウ")
      user.valid?
      expect(user.errors[:given_name]).to include("は全角で入力してください")
    end


    it "名字(カナ)が全角で入力されていないと進めない(アルファベットver)" do
      user = build(:user,family_name_kana: "yamada")
      user.valid?
      expect(user.errors[:family_name_kana]).to include("は全角カタカナで入力してください")
    end
    it "名字(カナ)が全角で入力されていないと進めない(半角カタカナver)" do
      user = build(:user,family_name_kana: "ヤマダ")
      user.valid?
      expect(user.errors[:family_name_kana]).to include("は全角カタカナで入力してください")
    end


    it "名前(カナ)が全角入力されていないと進めない(アルファベットver)" do
      user = build(:user,given_name_kana: "tarou")
      user.valid?
      expect(user.errors[:given_name_kana]).to include("は全角カタカナで入力してください")
    end
    it "名前(カナ)が全角入力されていないと進めない(半角カタカナver)" do
      user = build(:user,given_name_kana:"タロウ")
      user.valid?
      expect(user.errors[:given_name_kana]).to include("は全角カタカナで入力してください")
    end

    it "名字(カナ)がカタカナで入力されていないと進めない(ひらがなver)" do
      user = build(:user,family_name_kana: "やまだ")
      user.valid?
      expect(user.errors[:family_name_kana]).to include("は全角カタカナで入力してください")
    end
    it "名字(カナ)がカタカナで入力されていないと進めない(漢字ver)" do
      user = build(:user,family_name_kana: "山田")
      user.valid?
      expect(user.errors[:family_name_kana]).to include("は全角カタカナで入力してください")
    end


    it "名前(カナ)がカタカナで入力されていないと進めない(ひらがなver)" do
      user = build(:user,given_name_kana: "たろう")
      user.valid?
      expect(user.errors[:given_name_kana]).to include("は全角カタカナで入力してください")
    end
    it "名前(カナ)がカタカナで入力されていないと進めない(漢字ver)" do
      user = build(:user,given_name_kana:"タロウ")
      user.valid?
      expect(user.errors[:given_name_kana]).to include("は全角カタカナで入力してください")
    end
  end
  context "一意性" do
    it "すでに登録されているメールアドレスは登録できない" do
      User.create(
        nickname: "フリマ",
        email: "japan@gmail",
        password: "japan12",
        password_confirmation: "japan12",
        family_name: "山田",
        given_name: "太郎",
        family_name_kana: "ヤマダ",
        given_name_kana: "タロウ",
        birthday: "20200523",
      )
      user = User.new(
        nickname: "フリマ子",
        email: "japan@gmail",
        password: "japan122",
        password_confirmation: "japan122",
        family_name: "山田",
        given_name: "花子",
        family_name_kana: "ヤマダ",
        given_name_kana: "ハナコ",
        birthday: "20200524",
      )
      user.valid?
      expect(user.errors[:email]).to include("はすでに存在します")
    end
  end
end
/spec/factories/user.rb
FactoryBot.define do
  factory :user do
    nickname {"フリマ"}
    email{"japan@gmail"}
    password{"japan12"}
    password_confirmation{"japan12"}
    family_name{"山田"}
    given_name{"太郎"}
    family_name_kana{"ヤマダ"}
    given_name_kana{"タロウ"}
    birthday{20200523}
  end
end

テストのコードを記述したら、ターミナルで下記コマンドを実行する

ターミナル
 %  bundle exec rspec

ターミナルが下記のように全て緑になれば、結果と仮説が一致していることを意味しています
スクリーンショット 2020-06-12 19.31.00.png

controllerテスト

コントローラ内の処理が作動するか確認しました

require 'rails_helper'
RSpec.describe Users::RegistrationsController, type: :controller do
  describe 'GET #new' do
    it "レスポンスが成功" do
      @request.env["devise.mapping"] = Devise.mappings[:user]
      # ↑deviseを継承したcontrollerのテストの場合は、使用前にrequestする必要があるが、テストでは"route.rb"が使えないので明確に示す必要があります
      get :new
      expect(response).to be_successful
    end

    it "200ポンスを返すこと" do
      @request.env["devise.mapping"] = Devise.mappings[:user]
      post :new
      expect(response).to have_http_status "200"
    end
  end
  describe 'new_profileaddresses' do
    render_views
    context '有効なデータの場合' do
      before do
        @user = FactoryBot.attributes_for(:user_registration)
                                         # ↑:user_registrationはfactory_botで作成されたインスタンスです
        # ↑のattributes_forはデータをハッシュに入っている形にします
        @birthday= {"birthday(1i)"=>"2018", "birthday(2i)"=>"3", "birthday(3i)"=>"3"}
      end
      it "決まったviewを返す" do
        @request.env["devise.mapping"] = Devise.mappings[:user]
        expect(post :new_profileaddresses, params: {birthday: @birthday,user: @user}).to render_template("users/registrations/new_profileaddresses")
      end
    end
    context '無効なデータの場合' do
      before do
        @user = FactoryBot.attributes_for(:user_registration,nickname:"")
        @birthday= {"birthday(1i)"=>"2018", "birthday(2i)"=>"3", "birthday(3i)"=>"3"}
      end
      it "決まったviewを返す" do
        @request.env["devise.mapping"] = Devise.mappings[:user]
        expect(post :new_profileaddresses, params: {birthday: @birthday,user: @user}).to render_template("users/registrations/new")
      end
    end
  end
  describe 'new_deliveryaddresses' do
    render_views
    context '有効なデータの場合' do
      before do
        @data = User.new(FactoryBot.attributes_for(:user_registration_2))
        @profileaddress = FactoryBot.attributes_for(:profileaddress_registration)
      end
      it "決まったviewを返す" do
        @request.env["devise.mapping"] = Devise.mappings[:user]
        session["devise.regist_data"] = {"user" => @data.attributes}
        session["devise.regist_data"]["user"]["password"] = @data["password"]
        @user = session["devise.regist_data"]
        expect(post :new_deliveryaddresses, params: {profileaddress: @profileaddress}).to render_template("users/registrations/new_deliveryaddresses")
        # render_templateは()内のページに飛ぶはずということを意味しています
      end
    end
    context '無効なデータの場合' do
      before do
        @data = User.new(FactoryBot.attributes_for(:user_registration_2))
        @profileaddress = FactoryBot.attributes_for(:profileaddress_registration,postal_code: "111-1111")
      end
      it "決まったviewを返す" do
        @request.env["devise.mapping"] = Devise.mappings[:user]
        session["devise.regist_data"] = {"user" => @data.attributes}
        session["devise.regist_data"]["user"]["password"] = @data["password"]
        @user = session["devise.regist_data"]
        expect(post :new_deliveryaddresses, params: {profileaddress: @profileaddress}).to render_template("users/registrations/new_profileaddresses")
      end
    end
  end
  describe 'create' do
    render_views
    context 'バリデーション' do
      before do
        @request.host = "localhost:3000"
        # ↑root設定
        @password = FactoryBot.attributes_for(:user_registration_2)
        @data = User.new(FactoryBot.attributes_for(:user_registration_2))
        @profileaddress = Profileaddress.new(FactoryBot.attributes_for(:profileaddress_registration))
      end
      it "バリデーションを通す" do
        @deliveryaddress = FactoryBot.attributes_for(:deliveryaddress_registration)
        @request.env["devise.mapping"] = Devise.mappings[:user]
        session["devise.regist_data"] = {"user" => @data.attributes}
        session["devise.regist_data"]["user"]["password"]= @password[:password]
        
        session["profileaddress"] = @profileaddress.attributes

        expect(post :create, params: {deliveryaddress: @deliveryaddress}).to redirect_to("http://localhost:3000/")
        # ↑バリデーションが通ったら、ホーム画面に飛ぶ
      end
      it "バリデーションを通らない" do
        @deliveryaddress = FactoryBot.attributes_for(:deliveryaddress_registration,family_name:"")
        @request.env["devise.mapping"] = Devise.mappings[:user]
        session["devise.regist_data"] = {"user" => @data.attributes}
        session["devise.regist_data"]["user"]["password"]= @password[:password]
        
        session["profileaddress"] = @profileaddress.attributes

        expect(post :create, params: {deliveryaddress: @deliveryaddress}).to render_template("users/registrations/new_deliveryaddresses")
      end
    end
    context 'ユーザー登録' do
      before do
        @password = FactoryBot.attributes_for(:user_registration_2)
        @data = User.new(FactoryBot.attributes_for(:user_registration_2))
        @profileaddress = Profileaddress.new(FactoryBot.attributes_for(:profileaddress_registration))
      end
      it "登録できる" do
        @deliveryaddress = FactoryBot.attributes_for(:deliveryaddress_registration)
        @request.env["devise.mapping"] = Devise.mappings[:user]
        session["devise.regist_data"] = {"user" => @data.attributes}
        session["devise.regist_data"]["user"]["password"]= @password[:password]
        
        session["profileaddress"] = @profileaddress.attributes

        expect do
          post :create, params: {deliveryaddress: @deliveryaddress}
        end.to change{User.count}.by(1)
       
      end
    end
  end
end

require 'factory_bot'

FactoryBot.define do
  factory :user_registration,class: User do
    nickname {"フリマ太郎"}
    email {"japanjapan@gmail"}
    password {"japanjapan12"}
    password_confirmation {"japanjapan12"}
    family_name {"田中"}
    given_name {"太郎"}
    family_name_kana {"タナカ"}
    given_name_kana {"タロウ"}
  end
  factory :user_registration_2,class: User do
    nickname {"フリマ太郎"}
    email {"japanjapan@gmail"}
    password {"japanjapan12"}
    password_confirmation {"japanjapan12"}
    family_name {"田中"}
    given_name {"太郎"}
    family_name_kana {"タナカ"}
    given_name_kana {"タロウ"}
    birthday{"2018-03-03"}
  end

  factory :profileaddress_registration, class: Profileaddress do
    postal_code{"2222222"}
    prefectures{"北海道"}
    city{"根室"}
    address{"1−2−3"}
    building{"アパート104号"}
    user_id{}
  end

  factory :deliveryaddress_registration, class: Deliveryaddress do
    family_name{"田中"}
    given_name{"太郎"}
    family_name_kana{"タナカ"}
    given_name_kana{"タロウ"}
    postal_code{"2222222"}
    prefectures{"北海道"}
    city{"根室"}
    address{"1−2−3"}
    building{"アパート104号"}
    phone_number{"09027281594"}
    user_id{}
  end
end

controllerテストは
必要なデータが必要な配列、ハッシュに入っていることで求める処理を無事にするのかということを確かめるものだということを今回改めて理解できたと思います

controllerテストで困ったこと
ターミナルで、 % bundle exec rspecを行うと、何故かバリデーションに引っかかることがありました
エラーの中身はundefined method []' for nil:NilClassなのですが、何がnilなのかわかりませんでした
そこで、binding.pryを使用し、ターミナルで、何のデータが入っていないのか確認しました

  def create
    @user = User.new(session["devise.regist_data"]["user"])
    @profileaddress = Profileaddress.new(session["profileaddress"])
    @deliveryaddress = Deliveryaddress.new(deliveryaddresses_params)
    unless @deliveryaddress.valid?
      flash.now[:alert] = @deliveryaddress.errors.full_messages
      render :new_deliveryaddresses and return
    end
    @user.build_profileaddress(@profileaddress.attributes)
    @user.build_deliveryaddress(@deliveryaddress.attributes)
    binding.pry
    if @user.save
      session["devise.regist_data"]["user"].clear
      session["profileaddress"].clear
      sign_in(:user,@user)
      redirect_to root_path
    else
      render :new
    end
  end

ターミナル
[1] pry(#<Users::RegistrationsController>)> @user.save!

今回のことで、save!をすることで、saveをfalseする要因が確認できることを知ることができました

【参考】

【初心者向け】テストコードの方針を考える(何をテストすべきか?どんなテストを書くべきか?)
【Rspec】attributes_forってなんだ...

0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?