【この記事の説明】
現在、私はプログラミングスクールでプログラミング(主に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は非推奨とのことでした
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環境にのみあるようにしましょう
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
を記述します
--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モデルのみ記載したいと思います
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
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
ターミナルが下記のように全て緑になれば、結果と仮説が一致していることを意味しています
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ってなんだ...