2010-11-07 19 views
23

Tengo un modelo como este:¿Cómo probar qué validación falló en ActiveRecord?

class User < ActiveRecord::Base 
    validates_length_of :name, :in => (2..5) 
end 

Quiero poner a prueba esta validación:

it "should not allow too short name" do 
    u = User.new(:name => "a") 
    u.valid? 
    u.should have(1).error_on(:name) 
end 

Pero entonces no prueba qué tipo de error se encuentra en name. Quiero saber si fue too_short, too_long, o tal vez falló alguna otra validación.

que puede buscar el texto del mensaje en errores matriz, así:

u.errors[:name].should include(I18n.t("activerecord.errors.models.user.attributes.name.too_short")) 

Pero esto se producirá un error cuando me puse activerecord.errors.messages.too_short en el archivo local en lugar de mensaje específica del modelo.

Entonces, ¿es posible verificar qué tipo de error ocurrió?

Respuesta

38

Rails added an method to query for errors to ActiveModel in late 2011 and Rails v3.2. Sólo tienes que comprobar para ver si el error correspondiente ha sido #added?:

# An error added manually 
record.errors.add :name, :blank 
record.errors.added? :name, :blank # => true 

# An error added after validation 
record.email = '[email protected]' 
record.valid? # => false 
record.errors.added? :email, :taken # => true 

Tenga en cuenta que para las validaciones que son parametrizados (por ejemplo :greater_than_or_equal_to) que necesita para pasar el valor del parámetro también.

record.errors.add(:age, :greater_than_or_equal_to, count: 1) 
record.errors.added?(:age, :greater_than_or_equal_to, count: 1) 

Los errores se identifican por su clave i18n. Puede encontrar las claves apropiadas para verificar en el archivo Rails i18n apropiado para cualquier idioma bajo el error section.

Algunas otras preguntas ingeniosas y pregunta directamente ActiveModel#Error son #empty? y #include?(attr), así como cualquier cosa que usted puede hacer una Enumerable.

+1

agregado? es un método útil. Combinado con la sintaxis rspec esto podría hacer que se lea muy bien. P.ej. 'expect (record.errors) .to be_added: name,: blank' – PhilT

+3

Tenga en cuenta que debe pasar opciones como el tercer parámetro para algunas comprobaciones. por ejemplo, 'assert record.errors.added? (: slug,: too_short, count: 5)' donde 5 es la longitud requerida. –

+0

Tenga en cuenta que esta solución solo funciona para validaciones de modelo. Agregar errores arbitrarios no se puede verificar con '#added?' Por ejemplo, '#added?' Arrojará un error si se busca un error si ': name' o': email' en el ejemplo anterior no es un atributo de modelo. – sealocal

5

Recomiendo consultar la gema shoulda para manejar estos tipos de pruebas de validación repetitivas. Es un complemento de RSpec o Test :: Unidad para que pueda escribir las especificaciones concisas como:

describe User do 
    it { should ensure_length_of(:name).is_at_least(2).is_at_most(5) } 
end 
+0

Solo mirando el código para esto, parece que solo busca la traducción del mensaje también. Se ve bien, sin embargo, y al menos de esta manera el código se mantiene como parte de una biblioteca dedicada, no como parte de la aplicación. –

+0

Gracias por sus respuestas. Mi objetivo era probar un escenario de validación más complejo, por lo que no quería usar shoulda. Supongo que si debería funcionar de esta manera, no hay otra forma de verificar qué validación falló. –

12

que realmente no les gusta la idea de buscar mensajes de error traducidos en errores de hash. Después de una conversación con otros Rubyists, terminé el hash de corrección de errores de mono, por lo que primero guarda el mensaje no traducido.

module ActiveModel 
    class Errors 
    def error_names 
     @_error_names ||= { } 
    end 

    def add_with_save_names(attribute, message = nil, options = {}) 
     message ||= :invalid 
     if message.is_a?(Proc) 
     message = message.call 
     end 
     error_names[attribute] ||= [] 
     error_names[attribute] << message 
     add_without_save_names(attribute, message, options) 
    end 

    alias_method_chain :add, :save_names 
    end 
end 

A continuación, puede probar de esta manera:

u = User.new(:name => "a") 
u.valid? 
u.errors.error_names[:name].should include(:too_short) 
+3

Los raíles deberían hacer esto totalmente ... si tiene tipos de validación, el hecho de que no persistan en el objeto de error es extraño y frustrante. –

+3

@MikeCampbell Esta respuesta está un poco desactualizada. Rails v3.2 proporciona esta capacidad mediante 'Error # added?'. Ver [mi respuesta] (http://stackoverflow.com/questions/4119379/how-to-test-which-validation-failed-in-activerecord/#16800379) para más información. – fny

+0

@faraz, eso es increíble. No puedo creer que no pude encontrar eso. ¡Gracias! –

4

El enfoque que utiliza:

it "should not allow too short name" do 
    u = User.new(:name => "a") 
    expect{u.save!}.to raise_exception(/Name is too short/) 
end 

utilizo una expresión regular para que coincida con el mensaje de excepción, porque puede haber muchos mensajes de validación de el mensaje de excepción, pero queremos asegurarnos de que contenga un fragmento específico relacionado con el nombre que es demasiado corto.

Este enfoque empareja sus afirmaciones con sus mensajes de validación, por lo que si modifica su mensaje de validación probablemente necesite modificar sus especificaciones también. Sin embargo, en general, esta es una forma simple de hacer valer las validaciones haciendo su trabajo.

+0

Pero esto guarda el registro si falla la prueba. Otros efectos secundarios también pueden ocurrir. ¿Por qué no usar válido? para asegurarte de que solo estás probando la validación? – PhilT

Cuestiones relacionadas