Ruby
Rails
RSpec

開発日記その6〜Rspec controllerテストの修正(主にcreate)

はじめに

Qiita,Rails初心者なので至らないところ多々あります
開発環境はその1の記事を参照ください

開発していく

FactoryBotを上手く動かす

新しく設定を追加する

今までFactoryBot.attributes.forでしか動かなかったものを
rails.helperrequire 'factory_bot'と記述
更にしっかり定義されてるのに動かないFactoryBotには

spec_helper.rb
RSpec.configure do |config|
  config.before(:all) do
    FactoryBot.reload
  end
end

を記述しておく

参考:spec/factories以下に正しいfactoryの定義が存在するのにFactory not registered
springのせいで色々エラー起きてるけど最初から対策するにはどうしたらいいんだろう

FactoryBotのデータの名前を変える

分かりやすくするために名前をただのレシピから変える
factory :name do... endで名前を指定するのだけど,
:nameがモデルの名前と違う時は
factory :different_name, class: :model do...end
の形で記述が必要なので注意

Rspecのbeforeとlet

動作を先に記述しておく時にbeforeやletをよく使う(らしい)のだが,beforeとletはそれぞれ単体で使わないとエラーが出る
beforeはまとめて記述できるが,let!は一文のみ(毎回,一番最初)
letは変数を使う前にその変数に代入したりしてくれる(直前)
letだと直前に動く性質上上手く動かないこともあるらしいので注意する(そういう時はlet!にすると通る)

参考:RSpecのletを使うのはどんなときか?(翻訳)

controller#indexの修正

Recipe.allで想定したデータが入っているかのテスト
# 今までは一個ずつデータを作ってた
@recipe = post(:create, ...)
@other_recipe = post(:create, ...)

...

it '@recipeにすべてのレシピを割り当てる' do
  expect(assigns(:recipes)).to eq match_array([@recipe, 
  @other_recipe])
end

# 下記の@recipe同様テスト環境なので色々データが入ってる
# controllerの方の変数に入るものとは別のデータも多いのでエラーが出てた

# 解決方法(後で唯一性に引っかかるかもしれないので暫定)
it '@recipeにすべてのレシピを割り当てる' do
  recipes = create_list(:recipe_with_name_and_valid_url, 3)
  expect(assigns(:recipes)).to eq recipes  
end

# => Green

assigns(key)match_arrayを使えば唯一性の問題もクリアできるかと思ったけど

     Failure/Error: expect(assigns(:recipes)).to match_array([assigns(:recipe), assigns(:other_recipe)])

       expected collection contained:  [#<Recipe id: 2, name: "Yahoo", url: "yahoo.com", created_at: "2018-10-09 16:14:14", updated_at: "2018-10-09 16:14:14">, nil]
       actual collection contained:    [#<Recipe id: 1, name: "Google", url: "https://www.google.com/", created_at: "2018-10-09 16:14:14", u...e: "Yahoo", url: "yahoo.com", created_at: "2018-10-09 16:14:14", updated_at: "2018-10-09 16:14:14">]
       the missing elements were:      [nil]
       the extra elements were:        [#<Recipe id: 1, name: "Google", url: "https://www.google.com/", created_at: "2018-10-09 16:14:14", updated_at: "2018-10-09 16:14:14">]

上書きされてるような挙動だったので一旦保留にすることにした
assignsの使い方も怪しいので.

参考:RSpecにおけるFactoryGirlまとめ

controller#createの修正

ちゃんとコントローラー書いても案の定テストはエラーになるのでテストを治す

post createするとテストが大量に作られる

大量に作られると言うか前のデータが残ってしまう様子.
対策としては

rails_helper.rb
RSpec.configure do |config|
  config.use_transactional_fixtures = true
end

を記述してrails db:resetすると解決する

save確認のテスト
@recipe = post(:create, params: {recipe: {@valid_recipe}})

...

it 'saveできる' do
  expect(@recipe).to change(Recipe, :count).by(1)
end

# => expected `Recipe.count` to have changed by 1, but was not given a block

# 解決方法
it 'DBに新しいレシピが登録される' do
  expect do
    post(:create, params: {recipe: @recipe_with_name_and_valid_url})
    end.to change(Recipe, :count).by(1)
  end

# =>Green

redirectに使うidを取得したい
it 'showにredirect' do
  expect(response).to redirect_to recipe_path(@recipe.id)
end

# => @recipeの中にはidがないのでエラーがでる
# (pryで見てみるとテスト環境なのでネストの奥深くに置いてあったけど取り出せなかった)

# 解決方法
it 'showにredirect' do
  expect(response).to redirect_to recipe_path(assigns(:recipe).id)
end

# => Green

これでcreateのテストはまだ実装してない部分以外は無事に通り,未実装部分のテストも想定したエラーを出してるので一旦おしまい

おわり

進捗

・controller実装
・index, show, createの修正完了

次の予定

・controller#destroyの修正
・modelの実装

さいごに

テスト,一週間くらいかけて書いたので直すのも一苦労でした
調べても出ないのが辛い(調べ方が悪い)
もうテスト駆動開発ではなくなってると思うのですけど完成まで頑張ってこうと思います
その7