私は足場を使用してrspecコントローラーテストを生成しています。デフォルトでは、テストは次のように作成されます。
let(:valid_attributes) {
skip("Add a hash of attributes valid for your model")
}
describe "PUT update" do
describe "with valid params" do
let(:new_attributes) {
skip("Add a hash of attributes valid for your model")
}
it "updates the requested doctor" do
company = Company.create! valid_attributes
put :update, {:id => company.to_param, :company => new_attributes}, valid_session
company.reload
skip("Add assertions for updated state")
end
FactoryGirlを使用して、これに以下を入力しました。
let(:valid_attributes) { FactoryGirl.build(:company).attributes.symbolize_keys }
describe "PUT update" do
describe "with valid params" do
let(:new_attributes) { FactoryGirl.build(:company, name: 'New Name').attributes.symbolize_keys }
it "updates the requested company", focus: true do
company = Company.create! valid_attributes
put :update, {:id => company.to_param, :company => new_attributes}, valid_session
company.reload
expect(assigns(:company).attributes.symbolize_keys[:name]).to eq(new_attributes[:name])
これは機能しますが、変更された名前をテストするだけでなく、すべての属性をテストできるはずです。最後の行を次のように変更してみました:
class Hash
def delete_mutable_attributes
self.delete_if { |k, v| %w[id created_at updated_at].member?(k) }
end
end
expect(assigns(:company).attributes.delete_mutable_attributes.symbolize_keys).to eq(new_attributes)
それはほとんど機能しましたが、BigDecimalフィールドに関係するrspecから次のエラーが発生します。
-:latitude => #<BigDecimal:7fe376b430c8,'0.8137713195 830835E2',27(27)>,
-:longitude => #<BigDecimal:7fe376b43078,'-0.1270954650 1027958E3',27(27)>,
+:latitude => #<BigDecimal:7fe3767eadb8,'0.8137713195 830835E2',27(27)>,
+:longitude => #<BigDecimal:7fe3767ead40,'-0.1270954650 1027958E3',27(27)>,
Rspec、factory_girl、およびscaffoldingの使用は非常に一般的であるため、私の質問は次のとおりです。
有効なパラメーターを持つPUT更新のrspecおよびfactory_girlテストの良い例は何ですか? attributes.symbolize_keys
を使用して、変更可能なキーを削除する必要はありますか?これらのBigDecimalオブジェクトをeq
として評価するにはどうすればよいですか?
わかりました。これが私のやり方です。厳密にベストプラクティスに従うふりはしませんが、テストの精度、コードの明確さ、およびスイートの高速実行に焦点を当てています。
UserController
の例を見てみましょう
1-=コントローラにポストする属性を定義するためにFactoryGirlを使用しません。これらの属性の制御を保持したいからです。 FactoryGirlはレコードを作成するのに便利ですが、テストする操作に関連するデータは常に手動で設定する必要があります。読みやすさと一貫性の点で優れています。
これに関して、投稿された属性を手動で定義します
let(:valid_update_attributes) { {first_name: 'updated_first_name', last_name: 'updated_last_name'} }
2-次に、更新されたレコードに期待する属性を定義します。これは、ポストされた属性の正確なコピーにすることができますが、コントローラーが追加の作業を行うこともあり、それをテストしたい場合もあります。 。たとえば、ユーザーが個人情報を更新すると、コントローラーが自動的にneed_admin_validation
フラグを追加するという例を考えてみましょう
let(:expected_update_attributes) { valid_update_attributes.merge(need_admin_validation: true) }
これは、変更しないでおく必要がある属性のアサーションを追加できる場所でもあります。フィールドage
の例ですが、何でもかまいません
let(:expected_update_attributes) { valid_update_attributes.merge(age: 25, need_admin_validation: true) }
-let
ブロックでアクションを定義します。以前の2 let
と一緒に使用すると、仕様が非常に読みやすくなります。また、shared_examplesの記述も簡単になります
let(:action) { patch :update, format: :js, id: record.id, user: valid_update_attributes }
4-(その時点から、すべてはプロジェクトの共有サンプルとカスタムrspecマッチャーにあります)元のレコードを作成する時間です。そのため、FactoryGirlを使用できます。
let!(:record) { FactoryGirl.create :user, :with_our_custom_traits, age: 25 }
ご覧のように、age
アクション中に変更されていないことを確認するため、update
の値を手動で設定しています。また、工場ですでに25歳に設定されている場合でも、常に上書きするため、工場を変更してもテストが中断しません。
2番目に注意すること:ここではlet!
を強打して使用します。これは、コントローラーの失敗アクションをテストしたい場合があるためで、そのための最善の方法は、valid?
をスタブしてfalseを返すことです。 valid?
をスタブにすると、同じクラスのレコードを作成できなくなります。そのため、let!
を使用すると、beforevalid?
のスタブ
5-アサーション自体(そして最後に質問への回答)
before { action }
it {
assert_record_values record.reload, expected_update_attributes
is_expected.to redirect_to(record)
expect(controller.notice).to eq('User was successfully updated.')
}
要約したがって、上記すべてを追加すると、仕様は次のようになります
describe 'PATCH update' do
let(:valid_update_attributes) { {first_name: 'updated_first_name', last_name: 'updated_last_name'} }
let(:expected_update_attributes) { valid_update_attributes.merge(age: 25, need_admin_validation: true) }
let(:action) { patch :update, format: :js, id: record.id, user: valid_update_attributes }
let(:record) { FactoryGirl.create :user, :with_our_custom_traits, age: 25 }
before { action }
it {
assert_record_values record.reload, expected_update_attributes
is_expected.to redirect_to(record)
expect(controller.notice).to eq('User was successfully updated.')
}
end
assert_record_values
は、rspecを単純化するヘルパーです。
def assert_record_values(record, values)
values.each do |field, value|
record_value = record.send field
record_value = record_value.to_s if (record_value.is_a? BigDecimal and value.is_a? String) or (record_value.is_a? Date and value.is_a? String)
expect(record_value).to eq(value)
end
end
この単純なヘルパーでBigDecimal
を期待しているときにわかるように、次のように書くだけで、残りはヘルパーが行います
let(:expected_update_attributes) { {latitude: '0.8137713195'} }
つまり、最後に、そして結論として、shared_examples、ヘルパー、カスタムマッチャーを作成したら、仕様を超乾燥状態に保つことができます。コントローラーの仕様で同じことを繰り返し始めるとすぐに、これをリファクタリングする方法を見つけます。最初は時間がかかるかもしれませんが、完了すると数分でコントローラー全体のテストを書くことができます
そして最後の言葉(止められない、私はRspecが大好きです)は、私の完全なヘルパーがどのように見えるかです。モデルだけでなく、実際には何にでも使用できます。
def assert_records_values(records, values)
expect(records.length).to eq(values.count), "Expected <#{values.count}> number of records, got <#{records.count}>\n\nRecords:\n#{records.to_a}"
records.each_with_index do |record, index|
assert_record_values record, values[index], index: index
end
end
def assert_record_values(record, values, index: nil)
values.each do |field, value|
record_value = [field].flatten.inject(record) { |object, method| object.try :send, method }
record_value = record_value.to_s if (record_value.is_a? BigDecimal and value.is_a? String) or (record_value.is_a? Date and value.is_a? String)
expect_string_or_regexp record_value, value,
"#{"(index #{index}) " if index}<#{field}> value expected to be <#{value.inspect}>. Got <#{record_value.inspect}>"
end
end
def expect_string_or_regexp(value, expected, message = nil)
if expected.is_a? String
expect(value).to eq(expected), message
else
expect(value).to match(expected), message
end
end
これは質問者投稿です。ここで複数の重複する問題を理解するには、うさぎの穴を少し掘り下げる必要があったので、見つけた解決策について報告したいと思いました。
tldr;すべての重要な属性が変更されずにPUTから戻ってくることを確認しようとするのは、非常に困難です。変更された属性が期待どおりであることを確認してください。
私が遭遇した問題:
(Factory.build :company).attributes.symbolize_keys
の使用を提案します。Date.inspect
にはミリ秒が表示されないため、表示されません。Rails_spec.rbに入れることができるHashメソッドは次のとおりです。
class Hash
def symbolize_and_stringify
Hash[
self
.delete_if { |k, v| %w[id created_at updated_at].member?(k) }
.map { |k, v| [k.to_sym, v.to_s] }
]
end
end
別の方法として(そしておそらくできれば)、各属性を反復処理して値を個別に比較するカスタムrspecマッチャーを作成することもできます。これにより、日付の問題を回避できます。これが、@ Benjamin_Sinclaireが選択した回答の下部にあるassert_records_values
メソッドのアプローチでした(そのため、ありがとうございます)。
ただし、代わりにattributes_for
を使用して変更した属性を比較するというはるかに単純なアプローチに戻ることにしました。具体的には:
let(:valid_attributes) { FactoryGirl.attributes_for(:company) }
let(:valid_session) { {} }
describe "PUT update" do
describe "with valid params" do
let(:new_attributes) { FactoryGirl.attributes_for(:company, name: 'New Name') }
it "updates the requested company" do
company = Company.create! valid_attributes
put :update, {:id => company.to_param, :company => new_attributes}, valid_session
company.reload
expect(assigns(:company).attributes['name']).to match(new_attributes[:name])
end
この投稿により、他の人が私の調査を繰り返さないようにできることを願っています。
まあ、私はかなり簡単なことをしました。私は製作者を使用していますが、FactoryGirlでも同じだと確信しています。
let(:new_attributes) ( { "phone" => 87276251 } )
it "updates the requested patient" do
patient = Fabricate :patient
put :update, id: patient.to_param, patient: new_attributes
patient.reload
# skip("Add assertions for updated state")
expect(patient.attributes).to include( { "phone" => 87276251 } )
end
また、なぜあなたが新しい工場を建設しているのかはわかりません。PUT動詞は新しいものを追加するはずですよね?そして、最初に追加したもの(new_attributes
)、同じモデルのput
の後に偶然存在します。
このコードは、2つの問題を解決するために使用できます。
it "updates the requested patient" do
patient = Patient.create! valid_attributes
patient_before = JSON.parse(patient.to_json).symbolize_keys
put :update, { :id => patient.to_param, :patient => new_attributes }, valid_session
patient.reload
patient_after = JSON.parse(patient.to_json).symbolize_keys
patient_after.delete(:updated_at)
patient_after.keys.each do |attribute_name|
if new_attributes.keys.include? attribute_name
# expect updated attributes to have changed:
expect(patient_after[attribute_name]).to eq new_attributes[attribute_name].to_s
else
# expect non-updated attributes to not have changed:
expect(patient_after[attribute_name]).to eq patient_before[attribute_name]
end
end
end
JSONを使用して値を文字列表現に変換することにより、浮動小数点数を比較する問題を解決します。
また、新しい値が更新され、残りの属性が変更されていないことを確認する問題も解決します。
ただし、私の経験では、複雑さが増すにつれて、「更新しない属性が変更されないことを期待する」のではなく、特定のオブジェクトの状態をチェックすることが通常行われます。たとえば、「残りのアイテム」、「一部のステータス属性」など、コントローラで更新が行われると、他のいくつかの属性が変化することを想像してみてください。特定の予想される変更を確認したいのですが、更新されたものよりも多い場合があります。属性。
Railsアプリケーションをrspec-Rails gemでテストします。ユーザーの足場を作成しました。次に、user_controller_spec.rbのすべての例を渡す必要があります
これはすでにscaffoldジェネレーターによって書かれています。実装するだけ
let(:valid_attributes){ hash_of_your_attributes} .. like below
let(:valid_attributes) {{ first_name: "Virender", last_name: "Sehwag", gender: "Male"}
}
このファイルから多くの例を渡します。
Invalid_attributesの場合は、必ずフィールドのいずれかに検証を追加し、
let(:invalid_attributes) {{first_name: "br"}
}
ユーザーモデルでは、first_nameの検証は次のようになります=>
validates :first_name, length: {minimum: 5}, allow_blank: true
これで、ジェネレーターによって作成されたすべての例がこのcontroller_specに渡されます
これがPUTをテストする私の方法です。これは私の_notes_controller_spec
_のスニペットです。主なアイデアは明確でなければなりません(そうでない場合は教えてください)。
_RSpec.describe NotesController, :type => :controller do
let(:note) { FactoryGirl.create(:note) }
let(:valid_note_params) { FactoryGirl.attributes_for(:note) }
let(:request_params) { {} }
...
describe "PUT 'update'" do
subject { put 'update', request_params }
before(:each) { request_params[:id] = note.id }
context 'with valid note params' do
before(:each) { request_params[:note] = valid_note_params }
it 'updates the note in database' do
expect{ subject }.to change{ Note.where(valid_note_params).count }.by(1)
end
end
end
end
_
FactoryGirl.build(:company).attributes.symbolize_keys
の代わりに、FactoryGirl.attributes_for(:company)
と書きます。より短く、工場で指定したパラメーターのみが含まれています。
残念ながらあなたの質問について私が言えることはこれだけです。
追伸ただし、次のようなスタイルでBigDecimalの等価性チェックをデータベースレイヤーに配置するとします。
_expect{ subject }.to change{ Note.where(valid_note_params).count }.by(1)
_
これはあなたのために働くかもしれません。