Comment effectuer le test rspec put controller de scaffold
j'utilise un échafaudage pour générer des tests de contrôle rspec. Par défaut, il crée le test comme:
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
en utilisant FactoryGirl, j'ai rempli ceci avec:
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])
cela fonctionne, mais il semble que je devrais être en mesure de tester tous les attributs, au lieu de simplement tester le nom modifié. J'ai essayé de changer la dernière ligne:
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)
ça a presque marché, mais j'obtiens l'erreur suivante de 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)>,
utiliser rspec, factory_girl, et échafaudages est incroyablement commun, donc mes questions sont:
Quel est un bon exemple de test rspec et factory_girl pour une mise à jour PUT avec params valide?
Est-il nécessaire d'utiliser attributes.symbolize_keys
et de supprimer les clés mutables? Comment puis-je obtenir ces objets BigDecimal à évaluer comme eq
?
6 réponses
Ok donc c'est comme ça que je fais, Je ne fais pas semblant de suivre strictement les meilleures pratiques, mais je me concentre sur la précision de mes tests, la clarté de mon code, et l'exécution rapide de ma suite.
alors prenons un exemple de UserController
1- Je n'utilise pas FactoryGirl pour définir les attributs à poster à mon controller, parce que je veux garder le contrôle de ces attributs. FactoryGirl est utile pour créer un enregistrement, mais vous si vous définissez manuellement les données impliquées dans l'opération que vous testez, c'est mieux pour la lisibilité et la cohérence.
à cet égard, nous allons définir manuellement les attributs affichés
let(:valid_update_attributes) { {first_name: 'updated_first_name', last_name: 'updated_last_name'} }
2- puis je définis les attributs que j'attends pour l'enregistrement mis à jour, il peut être une copie exacte des attributs affichés, mais il peut être que le contrôleur faire un peu de travail supplémentaire et nous voulons également tester cela. Alors disons pour notre exemple: une fois que notre Utilisateur a mis à jour ses informations personnelles, notre contrôleur ajoute automatiquement un need_admin_validation
drapeau
let(:expected_update_attributes) { valid_update_attributes.merge(need_admin_validation: true) }
C'est aussi là que vous pouvez ajouter une assertion pour un attribut qui doit rester inchangé. Exemple avec le champ age
, mais il peut être n'importe quoi
let(:expected_update_attributes) { valid_update_attributes.merge(age: 25, need_admin_validation: true) }
3- je définis l'action, dans un bloc let
. Avec le précédent 2 let
je trouve qu'il fait mes spécifications très lisible. Et il rend également facile d'écrire shared_examples
let(:action) { patch :update, format: :js, id: record.id, user: valid_update_attributes }
4- (à partir de ce point tout est dans l'exemple partagé et RSpec personnalisé matchers dans mes projets) le temps de créer le disque original, pour que nous pouvons utiliser FactoryGirl
let!(:record) { FactoryGirl.create :user, :with_our_custom_traits, age: 25 }
comme vous pouvez le voir, nous avons défini manuellement la valeur de age
car nous voulons vérifier qu'elle n'a pas changé pendant l'action update
. Aussi, même si l'usine j'ai déjà fixé l'âge à 25 ans, je l'écrase toujours pour que mon test ne se casse pas si je change d'usine.
Deuxième chose à noter: ici, nous utilisons let!
avec un bang. C'est parce que parfois vous pouvez vouloir tester l'action de défaillance de votre contrôleur, et la meilleure façon de le faire est de couper valid?
et de retourner false. Une fois que vous appuyez sur valid?
vous ne pouvez plus créer des enregistrements pour la même classe, let!
avec un bang créerait l'enregistrement avant le talon de valid?
5- Les assertions lui-même (et, enfin, la réponse à votre question)
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.')
}
résumé donc en ajoutant tout ce qui précède, voici à quoi ressemble la spécification
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
est l'assistant qui va rendre votre rspece plus simple.
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
comme vous peut voir avec ce helper simple quand nous nous attendons à un BigDecimal
, nous pouvons juste écrire ce qui suit, et le helper faire le reste
let(:expected_update_attributes) { {latitude: '0.8137713195'} }
ainsi, à la fin, et pour conclure, lorsque vous avez écrit vos exemples shared_ex, helpers, and custom matchers, vous pouvez garder vos spécifications super DRY. Dès que vous commencez à répéter toujours la même chose dans vos contrôleurs spécifications de trouver comment vous pouvez restructurer. Cela peut prendre du temps au début, mais quand c'est fait, vous pouvez écrivez les tests pour un contrôleur entier en quelques minutes
et un dernier mot (Je ne peux pas m'arrêter, J'aime Rspec) voici à quoi ressemble mon aide complète. Il est utilisable pour n'importe quoi en fait, pas seulement les modèles.
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
c'est le message de l'auteur de la question. J'ai dû aller un peu dans le terrier du lapin pour comprendre les multiples problèmes qui se chevauchent ici, donc je voulais juste faire un rapport sur la solution que j'ai trouvée.
tldr; il est trop difficile d'essayer de confirmer que chaque attribut important revient inchangé d'un PUT. Il suffit de vérifier que l'attribut modifié est ce que vous attendez.
Les problèmes que j'ai rencontré:
- FactoryGirl.attributes_for ne renvoie pas toutes les valeurs, donc FactoryGirl: attributes_for ne me donne pas les attributs associés suggère d'utiliser
(Factory.build :company).attributes.symbolize_keys
, ce qui finit par créer de nouveaux problèmes. - spécifiquement, les Rails 4.1 enums montrent comme des entiers au lieu des valeurs enum, comme rapporté ici: https://github.com/thoughtbot/factory_girl/issues/680
- il s'avère que le BigDecimal question était un rouge hareng, causé par un bug dans le matcher rspec qui produit des diffs incorrects. Ceci a été établi ici: https://github.com/rspec/rspec-core/issues/1649
- la défaillance réelle de l'apparieur est causée par des valeurs de Date qui ne correspondent pas. Cela est dû au fait que le temps retourné est différent, mais il ne s'affiche pas parce que
Date.inspect
ne montre pas millisecondes. - j'ai contourné ces problèmes avec une méthode de hachage patché de singe qui symbolise les clés et les valeurs stringifs.
Voici la méthode Hash, qui pourrait aller dans rails_spec.rb:
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
alternativement (et peut-être de préférence) j'aurais pu écrire un RSpec personnalisé que itère à travers chaque attribut et compare leurs valeurs individuellement, ce qui aurait fonctionné autour de la question de date. C'était l'approche de la méthode assert_records_values
au bas de la réponse que j'ai choisie par @Benjamin_Sinclaire (pour qui, je vous remercie).
cependant, j'ai décidé à la place de revenir à l'approche beaucoup, beaucoup plus simple de rester avec attributes_for
et juste en comparant l'attribut que j'ai changé. Plus précisément:
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
j'espère que ce billet permettra aux autres de ne pas répéter mes recherches.
Eh bien, j'ai fait quelque chose de plus simple, J'utilise un manufacturier, mais je suis assez sûr que C'est la même chose avec 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
aussi, je ne suis pas sûr pourquoi vous construisez une nouvelle usine, mettre verbe est censé ajouter de nouveaux trucs, Non?. Et ce que vous testez si ce que vous avez ajouté en premier lieu ( new_attributes
), se trouve exister après le put
dans le même modèle.
ce code peut être utilisé pour résoudre vos deux problèmes:
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
il résout le problème de la comparaison des nombres à virgule flottante en convertissant les valeurs en représentation de chaîne de caractères à L'aide de JSON.
il résout également le problème de vérifier que les nouvelles valeurs ont été mises à jour, mais le reste des attributs n'ont pas changé.
Dans mon expérience, cependant, que la complexité augmente, la chose à faire est de vérifier certains l'état de l'objet spécifique au lieu de"s'attendre à ce que les attributs que je ne mets pas à jour ne changent pas". Imaginez, par exemple, que d'autres attributs changent au fur et à mesure que la mise à jour est effectuée dans le contrôleur, comme "éléments restants", "certains attributs de statut"... Vous souhaitez vérifier les changements spécifiques attendus, qui peuvent être plus que les attributs mis à jour.
voici ma façon de tester mis. C'est un extrait de mon notes_controller_spec
, l'idée principale doit être claire (dites-moi si non):
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
au lieu de FactoryGirl.build(:company).attributes.symbolize_keys
, j'écrirais FactoryGirl.attributes_for(:company)
. Il est plus court et ne contient que les paramètres que vous avez spécifiés dans votre usine.
malheureusement, c'est tout ce que je peux dire sur vos questions.
Mais si vous posez BigDecimal equality vérifiez sur la couche de base de données en écrivant dans le style comme
expect{ subject }.to change{ Note.where(valid_note_params).count }.by(1)
ça peut marcher pour vous.
Test de l'application rails avec rspec-gem rails. Créé à l'échafaudage de l'utilisateur. Vous devez maintenant passer tous les exemples de user_controller_spec.rb
cela a déjà été écrit par le générateur d'échafaudages. Il suffit de mettre en œuvre
let(:valid_attributes){ hash_of_your_attributes} .. like below
let(:valid_attributes) {{ first_name: "Virender", last_name: "Sehwag", gender: "Male"}
}
va maintenant passer de nombreux exemples de ce fichier.
pour invalid_attributes assurez - vous d'ajouter les validations sur l'un des champs et
let(:invalid_attributes) {{first_name: "br"}
}
dans le les utilisateurs du modèle .. la validation pour first_name est as = >
validates :first_name, length: {minimum: 5}, allow_blank: true
maintenant tous les exemples créés par les générateurs passeront pour ce controller_spec