tutorial rails ruby-on-rails rspec factory-bot rspec-rails scaffolding

ruby on rails - rails - Cómo completar la prueba del controlador rspec put desde un andamio



rspec rails 5 (6)

Aquí está mi manera de probar PUT. Ese es un fragmento de mi notes_controller_spec , la idea principal debe ser clara (dime si no):

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

En lugar de FactoryGirl.build(:company).attributes.symbolize_keys , escribiría FactoryGirl.attributes_for(:company) . Es más corto y contiene solo los parámetros que especificó en su fábrica.

Desafortunadamente, eso es todo lo que puedo decir acerca de sus preguntas.

PD: aunque si coloca BigDecimal, compruebe la igualdad en la capa de base de datos escribiendo con estilo

expect{ subject }.to change{ Note.where(valid_note_params).count }.by(1)

Esto puede funcionar para usted.

Estoy usando andamios para generar pruebas de controlador rspec. Por defecto, crea la prueba como:

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

Usando FactoryGirl, he completado esto con:

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])

Esto funciona, pero parece que debería poder probar todos los atributos, en lugar de solo probar el nombre cambiado. Intenté cambiar la última línea a:

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)

Casi funciona, pero recibo el siguiente error de rspec relacionado con los campos BigDecimal:

-: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)>,

Usar rspec, factory_girl y andamios es increíblemente común, por lo que mis preguntas son:

¿Cuál es un buen ejemplo de una prueba rspec y factory_girl para una actualización PUT con parámetros válidos? ¿Es necesario usar attributes.symbolize_keys y eliminar las claves mutables? ¿Cómo puedo obtener esos objetos BigDecimal para evaluar como eq ?


Bueno, hice algo que es bastante más simple, estoy usando Fabricator, pero estoy bastante seguro de que es lo mismo con 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

Además, no estoy seguro de por qué estás construyendo una nueva fábrica, se supone que el verbo PUT agrega nuevas cosas, ¿no? Y lo que estás probando si lo que agregaste en primer lugar ( new_attributes ), existe después de la new_attributes en el mismo modelo.


Esta es la publicación del interrogador. Tuve que ir un poco por el agujero del conejo para comprender múltiples problemas superpuestos aquí, así que solo quería informar sobre la solución que encontré.

tldr; Es demasiado complicado tratar de confirmar que todos los atributos importantes vuelven sin cambios desde un PUT. Solo verifica que el atributo cambiado sea lo que esperas.

Los problemas que encontré:

  1. FactoryGirl.attributes_for no devuelve todos los valores, por lo que FactoryGirl: attributes_for no me da los atributos asociados sugiere usar (Factory.build :company).attributes.symbolize_keys , que termina creando nuevos problemas.
  2. Específicamente, las enumeraciones de Rails 4.1 se muestran como enteros en lugar de valores de enumeración, como se informa aquí: https://github.com/thoughtbot/factory_girl/issues/680
  3. Resulta que el problema de BigDecimal era una pista falsa, causada por un error en el emparejador rspec que produce diferencias incorrectas. Esto se estableció aquí: https://github.com/rspec/rspec-core/issues/1649
  4. La falla del emparejador real es causada por los valores de fecha que no coinciden. Esto se debe a que el tiempo devuelto es diferente, pero no se muestra porque Date.inspect no muestra milisegundos.
  5. Resolví estos problemas con un método Hash parcheado que simboliza las claves y los valores de cadena de caracteres.

Aquí está el método Hash, que podría ir en 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

Alternativamente (y tal vez de preferencia) podría haber escrito un emparejador de rspec personalizado que itera a través de cada atributo y compara sus valores individualmente, lo que habría funcionado en torno a la fecha de emisión. Ese fue el enfoque del método assert_records_values en la parte inferior de la respuesta que seleccioné por @Benjamin_Sinclaire (por lo cual, gracias).

Sin embargo, decidí, en cambio, volver al enfoque mucho más simple de seguir con los attributes_for y simplemente comparar el atributo que cambié. Específicamente:

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

Espero que este post permita a otros evitar repetir mis investigaciones.


Este código se puede utilizar para resolver sus dos problemas:

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

Resuelve el problema de comparar números de punto flotante convirtiendo los valores en una representación de cadena utilizando JSON.

También resuelve el problema de verificar que los nuevos valores se hayan actualizado pero que el resto de los atributos no hayan cambiado.

Sin embargo, según mi experiencia, a medida que aumenta la complejidad, lo habitual es verificar el estado de un objeto específico en lugar de "esperar que los atributos que no actualizo no cambien". Imagine, por ejemplo, que algunos otros atributos cambien a medida que se realiza la actualización en el controlador, como "elementos restantes", "algunos atributos de estado" ... Le gustaría verificar los cambios específicos esperados, que pueden ser más que los actualizados atributos


Ok, así es como lo hago, no pretendo seguir estrictamente las mejores prácticas, pero me concentro en la precisión de mis pruebas, la claridad de mi código y la rápida ejecución de mi suite.

Así que vamos a tomar el ejemplo de un UserController

1- No uso FactoryGirl para definir los atributos para publicar en mi controlador, porque quiero mantener el control de esos atributos. FactoryGirl es útil para crear registros, pero siempre debe configurar manualmente los datos involucrados en la operación que está probando, es mejor para la legibilidad y la coherencia.

En este sentido definiremos manualmente los atributos publicados.

let(:valid_update_attributes) { {first_name: ''updated_first_name'', last_name: ''updated_last_name''} }

2- Luego defino los atributos que espero para el registro actualizado, puede ser una copia exacta de los atributos publicados, pero puede ser que el controlador haga un trabajo adicional y también queremos probar eso. Digamos, por ejemplo, que una vez que nuestro usuario actualizó su información personal, nuestro controlador agregará automáticamente un need_admin_validation

let(:expected_update_attributes) { valid_update_attributes.merge(need_admin_validation: true) }

También es donde puede agregar una aserción para un atributo que debe permanecer sin cambios. Ejemplo con la age campo, pero puede ser cualquier cosa.

let(:expected_update_attributes) { valid_update_attributes.merge(age: 25, need_admin_validation: true) }

3- Defino la acción, en un bloque let . Junto con los 2 anteriores, let encontrar que hace que mis especificaciones sean muy legibles. Y también hace fácil escribir ejemplos_compliados

let(:action) { patch :update, format: :js, id: record.id, user: valid_update_attributes }

4- (a partir de ese momento, todo está en ejemplos compartidos y concordantes de rspec personalizados en mis proyectos) Es hora de crear el registro original, para eso podemos usar FactoryGirl

let!(:record) { FactoryGirl.create :user, :with_our_custom_traits, age: 25 }

Como puede ver, configuramos manualmente el valor para la age ya que queremos verificar que no haya cambiado durante la acción de update . Además, incluso si la fábrica ya estableció la edad en 25, siempre la sobrescribo para que mi prueba no se rompa si cambio la fábrica.

Lo segundo a tener en cuenta: aquí usamos let! con una explosión. Esto se debe a que a veces es posible que desee probar la acción fallida de su controlador, y la mejor manera de hacerlo es aplazar la valid? y devuelve falso. ¿Una vez que aparezcas valid? ¡Ya no puedes crear registros para la misma clase, por lo tanto, let! con una explosión crearía el registro antes de la colilla de valid?

5- Las afirmaciones en sí (y finalmente la respuesta a tu pregunta)

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.'') }

Resumir Entonces, al agregar todo lo anterior, así es como se ve la especificación

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 es el ayudante que simplificará su 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

Como puede ver con este simple ayudante cuando esperamos un BigDecimal , podemos escribir lo siguiente y el ayudante hacer el resto

let(:expected_update_attributes) { {latitude: ''0.8137713195''} }

Así que al final, y para concluir, cuando haya escrito sus ejemplos compartidos, ayudantes y emparejadores personalizados, puede mantener sus especificaciones super DRY. Tan pronto como empieces a repetir lo mismo en las especificaciones de tus controladores, encuentra cómo puedes refactorizar esto. Puede llevar tiempo al principio, pero cuando haya terminado, puede escribir las pruebas para un controlador completo en pocos minutos

Y una última palabra (no puedo parar, me encanta Rspec) aquí es cómo se ve mi ayudante completo. Es utilizable para cualquier cosa, no solo modelos.

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


Probando la aplicación de rieles con rspec-rails gema. Creado el andamio del usuario. Ahora necesitas pasar todos los ejemplos para el user_controller_spec.rb

Esto ya lo ha escrito el generador de andamios. Solo implementa

let(:valid_attributes){ hash_of_your_attributes} .. like below let(:valid_attributes) {{ first_name: "Virender", last_name: "Sehwag", gender: "Male"} }

Ahora pasaremos muchos ejemplos de este archivo.

Para invalid_attributes asegúrese de agregar las validaciones en cualquiera de los campos y

let(:invalid_attributes) {{first_name: "br"} }

En el modelo de usuarios ... la validación de first_name es como =>

validates :first_name, length: {minimum: 5}, allow_blank: true

Ahora todos los ejemplos creados por los generadores pasarán para este controlador_spec