サンプルデータbuild、createどちらを使うか
概要、目的
サンプルデータの使い分け
テスト作成時に手こずったのでアウトプットも兼ねて
前提
使用するアプリのログイン機構はsorceryを使用
テストはrspec
FactoryBotを使用
今回使用するコード
# spec/factories/users.rb FactoryBot.define do factory :user do sequence(:email){|n| "test@#{n}example.com" } password { 'password' } password_confirmation { 'password' } end end _____________________________________________________________________ # spec/features/users_spec.rb RSpec.feature "Users", type: :feature do describe 'ログイン前' do # 変化させる箇所 create,buildした時の違い # ①let(:user) { create(:user) } # ②let(:user) { build(:user) } context 'フォームの入力値が正常' do it 'ユーザーの新規登録作成が成功する' do visit root_path click_link 'SignUp' fill_in 'Email', with: user.email fill_in 'Password', with: user.password fill_in 'Password confirmation', with: user.password_confirmation # ③byegug デバック時に使用 click_button 'SignUp' expect(page).to have_content 'User was successfully created.' end end end end
実現したいこと
図1のフォームが正常に動作し、ログインできるかのテスト
create,buildの使い分け
今回はサンプルデータをcreate,buildしてみてどのような違いがあるか、試していきます.
createしてテストすると...
結論から言うとcreateではテストはパスしません.
最初私はcreateしたデータで、テスト項目に当てはめればいいと思い、テストを作成しました.
今後テストを書いていく上で、同じ失敗を繰り返さないためにもアウトプットします.
上のコード①を有効にしてテストを実行します.
テスト結果
$ bundle exec rspec 1) Users ログイン前 フォームの入力値が正常 ユーザーの新規登録作成が成功する Failure/Error: expect(page).to have_content 'User was successfully created.' expected to find text "User was successfully created." in "Login SignUp\nSignUp\n3 errors prohibited this user from being saved:\nPassword is too short (minimum is 3 characters) Password confirmation can't be blank Email has already been taken\nEmail\nPassword\nPassword confirmation\nBack" # ./spec/features/users_spec.rb:15:in `block (4 levels) in <main>' Finished in 0.44038 seconds (files took 1.64 seconds to load) 18 examples, 1 failure, 8 pending Failed examples: rspec ./spec/features/users_spec.rb:7 # Users ログイン前 フォームの入力値が正常 ユーザーの新規登録作成が成功する
原因
どうなっているか上のコード③を有効にして再度テストを実行します.
userそのものは存在してるようです.
(byebug) user
#<User id: 1, email: "test@1example.com", crypted_password: "$2a$04$gk9IxtIsYvQIjxcluZv8R.iEycIUUU0DzF6EJMu62XC...", salt: "vD43wtyV-oauxVTaFAT_", created_at: "2021-02-26 02:49:46", updated_at: "2021-02-26 02:49:46">
各カラムに値があるか見ていきます.
(byebug) user.email "test@1example.com" (byebug) user.password nil (byebug) user.password_confirmation nil (byebug)
デバックの結果passwordカラムの値がnilのため、テスト時にnilの値が入力されたためにユーザー登録ができないことがわかります.
エラーメッセージを再度見直すと、パスワードが空だと言われています.
1) Users ログイン前 フォームの入力値が正常 ユーザーの新規登録作成が成功する Failure/Error: expect(page).to have_content 'User was successfully created.' expected to find text "User was successfully created." in "Login SignUp\nSignUp\n3 errors prohibited this user from being saved:\nPassword is too short (minimum is 3 characters) Password confirmation can't be blank Email has already been taken\nEmail\nPassword\nPassword confirmation\nBack" # ./spec/features/users_spec.rb:15:in `block (4 levels) in <main>'
ではなぜpasswordが空なのでしょうか?
コンソールでcreateした時と、buildした時の違いを見てます.
#create時 [1] pry(main)> create_user=FactoryBot.create(:user) (0.1ms) SAVEPOINT active_record_1 User Exists (0.2ms) SELECT 1 AS one FROM "users" WHERE "users"."email" = ? LIMIT ? [["email", "test@1example.com"], ["LIMIT", 1]] User Create (2.1ms) INSERT INTO "users" ("email", "crypted_password", "salt", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?) [["email", "test@1example.com"], ["crypted_password", "$2a$10$FooeA7t.Wprve0Z7JhgZsuryfRypjCT7dcIUjJzSIwWvronJ7Q2ue"], ["salt", "_uQ2mDDruScW1H6sxDy7"], ["created_at", "2021-02-26 03:20:14.766456"], ["updated_at", "2021-02-26 03:20:14.766456"]] (0.1ms) RELEASE SAVEPOINT active_record_1 => #<User:0x00007fa18bb05610 id: 7, email: "test@1example.com", crypted_password: "$2a$10$FooeA7t.Wprve0Z7JhgZsuryfRypjCT7dcIUjJzSIwWvronJ7Q2ue", salt: "_uQ2mDDruScW1H6sxDy7", created_at: Fri, 26 Feb 2021 03:20:14 UTC +00:00, updated_at: Fri, 26 Feb 2021 03:20:14 UTC +00:00> #build時 [4] pry(main)> build_user=FactoryBot.build(:user) => #<User:0x00007fa18b341b40 id: nil, email: "test@2example.com", crypted_password: nil, salt: nil, created_at: nil, updated_at: nil>
create時にはcrypted_passwod、saltの値が入っていて、build時にはnilということがわかります.
次にFactoryBotで定義されている値が入っているか確かめてます.
FactoryBotのコード(再掲載)
FactoryBot.define do factory :user do sequence(:email){|n| "test@#{n}example.com" } password { 'password' } password_confirmation { 'password' } end end # ここからコンソール上のコードと比べながら # create_user = FactryBot.create(:user) # build_user = FactryBot.build(:user) # create時 [7] pry(main)> create_user.email => "test@1example.com" [8] pry(main)> create_user.password => nil [9] pry(main)> create_user.password_confirmation => nil #build時 [10] pry(main)> build_user.email => "test@2example.com" [11] pry(main)> build_user.password => "password" [12] pry(main)> build_user.password_confirmation => "password" [13] pry(main)>
テスト失敗時デバックした時と同じように、createするとpasswod,password_confirmationがnilになっています.
反対にbuildではFactryBotで定義した通りに、password,password_confirmationカラムに値が入っています.
create時はpasword,password_confirmationがnilになり、build時には値が入っていることがわかりました.
ではなぜcreate時はnilになるのでしょうか?
再度こちらのコードを見てみましょう.
#create時 [1] pry(main)> create_user=FactoryBot.create(:user) (0.1ms) SAVEPOINT active_record_1 User Exists (0.2ms) SELECT 1 AS one FROM "users" WHERE "users"."email" = ? LIMIT ? [["email", "test@1example.com"], ["LIMIT", 1]] User Create (2.1ms) INSERT INTO "users" ("email", "crypted_password", "salt", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?) [["email", "test@1example.com"], ["crypted_password", "$2a$10$FooeA7t.Wprve0Z7JhgZsuryfRypjCT7dcIUjJzSIwWvronJ7Q2ue"], ["salt", "_uQ2mDDruScW1H6sxDy7"], ["created_at", "2021-02-26 03:20:14.766456"], ["updated_at", "2021-02-26 03:20:14.766456"]] (0.1ms) RELEASE SAVEPOINT active_record_1 => #<User:0x00007fa18bb05610 id: 7, email: "test@1example.com", crypted_password: "$2a$10$FooeA7t.Wprve0Z7JhgZsuryfRypjCT7dcIUjJzSIwWvronJ7Q2ue", salt: "_uQ2mDDruScW1H6sxDy7", created_at: Fri, 26 Feb 2021 03:20:14 UTC +00:00, updated_at: Fri, 26 Feb 2021 03:20:14 UTC +00:00> #build時 [4] pry(main)> build_user=FactoryBot.build(:user) => #<User:0x00007fa18b341b40 id: nil, email: "test@2example.com", crypted_password: nil, salt: nil, created_at: nil, updated_at: nil>
create時にはpassword,password_confirmationカラムの値がnilに対して,crypted_passwordというカラムに値が代入されています.
これはpasswordをハッシュ化した値が代入されています.
ハッシュ化することで、悪意を持ったユーザーからpasswordを盗まれないようにするためです.
つまりnilになるのはsorceryのコードが関係しています.
corceryではユーザーが入力したpassword,password_confirmationカラムの値を同一か確認し、さらにpasswordをハッシュ化しcrypted_passwordに保存しています.
crypted_passwordに値が代入されたタイミングでpassword,password_confitmationにnilをセットしています.
nilをセットしているのは、悪意を持ったユーザーから身を守るためです.(直接データベースからカラムにアクセスされた際の対策)
以上がcreate時にpassword,password_confirmationがnilになり、テストがパスしない原因です.
最後にテストをbuildしたサンプルデータでテストをパスしましょう.
buildしてテスト
テストコード①を無効、②を有効にして、テストを実行すると成功します.
ログイン機構などをgemを利用していると、このようなことがあるので注意しましょう.
またそもそも今回のテストは新規登録が成功するかのテストなので、createしたユーザーでテストをするのはテストの意味が薄れてしまう.(実際のアプリの動作とは異なる)ので書いているテストが、実際のアプリの挙動と同じかどうかを確認しながら書きましょう.