テストコードの書き方が漠然としててよくわからん。
modelの評価はvalidかどうかを見るだけで、
controllerの評価はrender先とインスタンスが望んだものかを見るだけ。
なんだけど、新規データを保存する挙動の場合はインスタンスの中身の評価ではなくてデータテーブルに保存された行数を見ているのではなんでだろう?回りくどい上に正確なテストになってない気がするんだけど。
ruby
宇宙船演算子
A < Bなら-1を、A == Bなら0を、A > Bなら1を返す
sort
昇順で並び替え
array.sort
降順で並び替え
array.sort {|a, b| b<=>a }
compact, compact!(nilの要素を削除)
配列に対して使う。
select
条件に会う要素を取得する。
array.select { |a| 条件 }
delete_if
array = (1..5).to_a.delete_if { |el| el.even? } array = (1..5).to_a.delete_if(&:even?)
テストコード
bundle exec rspec
bundle exec rspec spec/controllers/tweets_controller_spec.rb
コントローラーに対するテスト
1つのアクションにつき、以下の2点を確かめる。
- インスタンス変数の値が期待したものになるか
- ビューに正しく遷移するか
describe ◯◯Controller do
describe 'HTTPメソッド名 #アクション名' do
it "インスタンス変数は期待した値になるか?" do
"擬似的にリクエストを行ったことにするコードを書く"
"エクスペクテーションを書く"
end
it "期待するビューに遷移するか?" do
"擬似的にリクエストを行ったことにするコードを書く"
"エクスペクテーションを書く"
end
end
gem rails-controller-testingのインストール
group :development, :test do
gem 'rails-controller-testing'
end
フォルダとファイル管理
spec/controllersフォルダを作成し、以下にcontrollerに対応したspecファイルを作成する。
例
hogehoge_controller.rb に対して
hogehoge_controller_spec.rbを作成。
getメソッド
httpメソッドをシンボル型にして渡す。(get, post, delete, patch)
describe 'GET #show' do
it "renders the :show template" do
get :show, params: { id: 1 }
end
end
response
example内でリクエストが行われた後の遷移先のビューの情報を持つインスタンス。
it "renders the :edit template" do
get :edit, params: { id: tweet }
expect(response).to render_template :edit
end
render_templateマッチャ
引数にシンボル型でアクション名を取る。
引数で指定したアクションがリクエストされた時に自動的に遷移するビューを返す。
matchマッチャ
引数に配列クラスのインスタンスをとり、expectの引数と比較するマッチャ。
配列の中身の順番までチェックしてくれる。
assignsメソッド
アクションで定義しているインスタンス変数をテストするためのメソッド。
引数に、直前でリクエストしたアクション内で定義されているインスタンス変数をシンボル型でとる。
例
it "assigns the requested tweet to @tweet" do
tweet = create(:tweet)
get :edit, params: { id: tweet }
expect(assigns(:tweet)).to eq tweet
end
同名のワードが何度も出てくるのに、それぞれ意味が違うので注意。
tweet = create(:tweet)
get :edit, params: { id: tweet }
expect(assigns(:tweet)).to eq tweet
青字のtweetは、FactoryBotへ渡す引数。factories/tweets.rbを示す。
赤字のtweetは、example内で生成したインスタンス。
緑字のtweetは、赤字のtweetを元にget #editを動かした結果、生成されたインスタンス。
この赤と緑のtweetをassignsで比較している。
context
context do ~ endで囲むと結果がブロックとして表示される。
context 'can save' do
it "is valid without a image" do
end
it "is valid without a body" do
end
it "is valid with body & image" do
end
end
Message
#create
is invalid without image&body
is invalid without a group_id
is invalid without a user_id
can save
is valid without a image
is valid without a body
is valid with body & image
contextで囲まれていないものが自動的に先に表示されて、context同士は上から順に並ぶ。
なおformat progressの場合には表示されないので意味なし。
CarrierWaveを使っているModelをRSpecでテスト
rspecでテストする際に画像を渡す場合は単に文字列を渡してはダメ。
任意の場所にファイルを置いて、file.openで画像を渡す。
image {File.open("#{Rails.root}/public/images/test_image.jpg")}
パスを連結する方法はjoinでも良い
Rails.root.join('spec/support/**/*.rb')
deviseをテストで利用する
ユーザーのログイン状態で検証項目が変わるため、準備が必要。
rspec下にsupportフォルダを作成し、module用にファイルを作成。
/spec/support/controller_macros.rb
module ControllerMacros
def login(user)
@request.env["devise.mapping"] = Devise.mappings[:user]
sign_in user
end
end
helperに、deviseのコントローラのテスト用のモジュールと先ほど定義したControllerMacrosを読み込む記述をする。
/spec/rails_helper.rb
RSpec.configure do |config|
Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
config.include Devise::Test::ControllerHelpers, type: :controller
config.include ControllerMacros, type: :controller
#〜省略〜
end
letメソッド
複数のexampleで同一のインスタンスを使いたい場合、letメソッドを使う。
遅延評価(lazy evaluation)のため、呼び出し時に一度評価された後は値がキャッシュされ、以降の呼び出し時に同じ値を吐く。かつキャッシュされているため処理が高速化する。
以下の例の場合、groupとuserは各expectationで使い回してOKなのでletでインスタンスを生成する。
describe MessagesController do
let(:group) { create(:group) }
let(:user) { create(:user) }
describe '#index' do
context 'log in' do
before do
login user
get :index, params: { group_id: group.id }
end
it 'assigns @message' do
expect(assigns(:message)).to be_a_new(Message)
end
it 'assigns @group' do
expect(assigns(:group)).to eq group
end
it 'redners index' do
expect(response).to render_template :index
end
end
end
end
before
beforeブロックの内部に記述された処理は、各exampleが実行される直前に、毎回実行される。
before do
login user
get :index, params: { group_id: group.id }
end
it do
end...
be_a_newマッチャ
対象が引数で指定したクラスのインスタンスかつ未保存のレコードであるかどうか確かめる。
it 'assigns @message' do
expect(assigns(:message)).to be_a_new(Message)
end
redirect_toマッチャ
引数にとったプレフィックスにリダイレクトした際の情報を返す。
context 'not log in' do
before do
get :index, params: { group_id: group.id }
end
it 'redirects to new_user_session_path' do
expect(response).to redirect_to(new_user_session_path)
end
end
FactoryBot
create_list
リソースを複数作成したい場合に利用する。
第1引数にリソースを、第2引数に作成したい数を指定する。
hoges = create_list(:hoge, 3)
Faker
emailや電話番号、名前などのダミーデータを作成するためのGem。
インストール後、factory_botの設定ファイルの中でFakerのメソッドを利用し、ダミーデータを生成する。
FactoryBot.define do
factory :user do
nickname {"abe"}
password {"00000000"}
password_confirmation {"00000000"}
sequence(:email) {Faker::Internet.email}
end
end
fakerのインストール
テスト環境にのみ必要なのでgemfileの以下に記述しbundle install
group :test do
gem 'faker'
end
fakerパターン例
email 重複回避
sequence(:email) {Faker::Internet.email}
email {Faker::Internet.free_email}
テストのやり方
4つのgemをインストール
web-consoleを入れていなければweb-consleも。
group :development, :test do
gem 'rspec-rails'
gem 'factory_bot_rails'
gem 'rails-controller-testing'
gem 'faker'
end
group :development do
gem 'web-console'
end
記入したらbundle install
設定ファイルを作成
rails g rspec:install
specフォルダに以下が作成される。
create .rspec
create spec
create spec/spec_helper.rb
create spec/rails_helper.rb
.rspecファイルに必要に応じて以下を追記
--format documentation
フォルダ作成
project/spec/models
project/spec/controllers
テストの疑問
前提としてfactorybotに以下の記述があるとして、
factories/message.rb
FactoryBot.define do
factory :message do
body {Faker::Lorem.sentence}
image {File.open("#{Rails.root}/public/images/cat1.png")}
user
group
end
end
it "is valid without a body" do
message = build(:message, body: nil)
binding.pry
message.valid?
expect(message).to be_valid
end
build直後で止めてmessageの中身を確認すると、すべてnil。
にも関わらずmessage.valid?はtrue。
pry> message
=> #<Message:0x00007f89391d5d48 id: nil, body: nil, image: nil, created_at: nil, updated_at: nil, user_id: nil, group_id: nil>
pry> message.valid?
=> true
bodyとimageはnull許可だけど、
validates :body, presence: true, unless: :image?
でモデルに片方は必要だとvalidationを掛けている。
どっちもnilでvalid?がtrueなのは何故だろう?
ではbuildをcreateにすると、どうなるか?
it "is valid without a body" do
message = create(:message, body: nil)
binding.pry
message.valid?
expect(message).to be_valid
end
こちらは値が入ってvalid?もOK.
pry> message
=> #<Message:0x00007feb4da4e198
id: 3,
body: nil,
image: "cat1.png",
created_at: Sat, 10 Aug 2019 10:37:08 UTC +00:00,
updated_at: Sat, 10 Aug 2019 10:37:08 UTC +00:00,
user_id: 3,
group_id: 3>
pry> message.valid?
=> true
不思議なのはbuildした段階ではimageに値が入っていないけど、saveすると値が入ってくること。しかもvalidationのチェックは値が入ったものとして行われている。何故だろう?
ちなみにbodyはbuildした段階で既に値が入っている。これが普通の挙動だと思うけどなぁ。
外部キーのエラーメッセージの格納場所はどこ?
user_idを外部キーに持つmessageテーブルを保存した時、エラーメッセージの格納場所がmessage.errors[:user_id]になるのかと思いきやmessage.errors[:user]に格納される。
has_many through で持っている影響だと思うけど、わかりにくい。
it "is invalid without a user_id" do
message = build(:message, user_id: nil)
message.valid?
expect(message.errors[:user]).to include("を入力してください")
end
haml
inputタグ
submitボタンを設置する場合は{}内に続けてtypeを指定する。
ボタンの文字列はvalueに書く。
%input.form__input--btn{type: "submit", value: "Post"}
リセットCSS
使い方を。_reset.cssに書いてimportする。
コメント