2012-02-13 12 views
6

Actualmente estoy luchando un poco tratando de mantener las especificaciones de mi controlador seco y sucinta y reduce a una afirmación por ejemplo. Me encuentro con algunas dificultades, especialmente con dónde colocar la llamada de solicitud de controlador real dentro de una estructura anidada para que coincida con los diversos casos extremos.especificaciones del controlador seco con RSpec

He aquí un ejemplo simplificado para demostrar el problema:

describe MyController do 
    let(:item) { Factory(:item) } 
    subject { response } 

    describe "GET #show" do 
    before(:each) do 
     get :show 
    end 

    context "published item" do 
     it { should redirect_to(success_url) } 
    end 

    context "unpublished item" do 
     before(:each) do 
     item.update_attribute(published: false) 
     end 

     it { should redirect_to(error_url) } 
    end 
    end 
end 

Es evidente que esto es un ejemplo artificial, pero que ilustra lo que me gustaría hacer y lo que no funciona. Principalmente, el problema es el bloque before en el contexto "no publicado". Lo que pasa es que el cambio que hice a los datos de configuración que realmente sucede después la llamada get debido a la forma en que los contextos están anidados, por lo que el ejemplo en ese contexto está trabajando realmente con el escenario inicial en lugar de la que yo propongo.

entiendo por qué sucede esto y cómo los contextos nido. Creo que lo que había como tener alguna manera de decirle RSpec lo que me gustaría que se ejecute justo después cualquier before ganchos sin embargo, justo antes de cualquier afirmación en un contexto dado. Esto sería perfecto para las especificaciones del controlador. Me gustaría aprovechar anidando en mi especificaciones del controlador para construir gradualmente las variaciones de los casos límite, sin tener que dispersar la llamada get o incluso una llamada a un ayudante do_get en cada una de mis afirmaciones it. Esto sería especialmente molesto para mantenerme sincronizado con cualquier macro personalizada it_should que estoy usando.

¿Hay algo en RSpec actualmente para lograr esto? ¿Hay algún truco que pueda usar para acercarme? Parecería perfectamente adecuado para la forma en que he visto a mucha gente escribiendo las especificaciones de su controlador; por lo que he encontrado, la gente básicamente se conformó con que se llamara a los asistentes do_get antes de cada afirmación. ¿Hay una mejor manera?

Respuesta

6

el Dry principio establece que "Cada pieza de conocimiento debe tener una única representación autorizada, sin ambigüedades, dentro de un sistema." Lo que estás haciendo es mucho más acerca de guardar unos pocos caracteres aquí y allá que mantener las cosas SECAS, y el resultado es una red enmarañada de dependencias arriba y abajo de una jerarquía que, como puedes ver, es una perra para llegar a hacer qué lo quieres y, en consecuencia, frágil y frágil.

Vamos a empezar con lo que se tiene en escrito en una manera que es prolijo y trabaja:

describe MyController do 
    describe "GET #show" do 
    context "published item" do 
     it "redirects to the success url" do 
     item = Factory(:item, published: true) 
     get :show, :id => item.id 
     response.should redirect_to success_url 
     end 
    end 

    context "unpublished item" do 
     it "redirects to the error url" do 
     item = Factory(:item, published: false) 
     get :show, :id => item.id 
     response.should redirect_to error_url 
     end 
    end 
    end 
end 

Ahora los únicos "piezas de conocimiento" que se están duplicados son los nombres de los ejemplos, lo que podría ser generado por los matchers al final de cada ejemplo.Esto se puede resolver de una manera legible utilizando el método example, que es un alias de it:

describe MyController do 
    describe "GET #show" do 
    context "published item" do 
     example do 
     item = Factory(:item, published: true) 
     get :show, :id => item.id 
     response.should redirect_to success_url 
     end 
    end 

    context "unpublished item" do 
     example do 
     item = Factory(:item, published: false) 
     get :show, :id => item.id 
     response.should redirect_to error_url 
     end 
    end 
    end 
end 

Hay. SECO. Y bastante legible y fácil de cambiar. Ahora, cuando le sucede a añadir más ejemplos para cualquiera de los contextos, se puede agregar un let:

describe MyController do 
    describe "GET #show" do 
    context "published item" do 
     let(:item) { Factory(:item, published: true) } 
     example do 
     get :show, :id => item.id 
     response.should redirect_to success_url 
     end 

     example do 
     # other example 
     end 
    end 
    # ... 
    end 
end 

ahora el único código duplicado (no es el mismo que el principio DRY) es el get. Si realmente lo sientes con fuerza, puedes delegar esas llamadas en un método como get_show(id) o algo así, pero en realidad no está comprando mucho en ese momento. No es como si la API para get fuera a cambiar, y el único argumento para get es la identificación item, que realmente le importa en el ejemplo (por lo que no hay información innecesaria).

En cuanto al uso de subject para capturar la respuesta y obtener una línea de la transacción, eso hace que las cosas realmente difíciles de leer y no le ahorre mucho. De hecho, he llegado a considerar usar subject de esta manera to be a smell.

Espero que esto ayude.

Saludos, David

+0

Buen punto. La verbosidad adicional puede valer la pena para mantener las especificaciones claras, incluso si la llamada 'get' todavía se repite. Aún así, parece que las especificaciones del controlador, que tienen un caso de uso muy específico para especificar una respuesta a una acción REST, de alguna manera podrían reducir dicha repetición. –

+0

Chris - Estoy de acuerdo en que algún tipo de atajo sería agradable y estoy abierto a la idea, pero aún veo uno que mantiene lo que siento es un nivel adecuado de claridad. Si tiene alguna idea, por favor envíe una solicitud de función a https://github.com/rspec/rspec-rails/issues. –

3

Will

context "unpublished item" do 
    let(:item) do 
    Factory(:item, published: false) 
    end 

    it { should redirect_to(error_url) } 
end 

trabajo para usted? Por cierto, before de forma predeterminada es before(:each) por lo que puede SECAR sus especificaciones un poco más.

ACTUALIZACIÓN: también se puede aislar con ejemplos anónimos contextos, como:

describe "GET #show" do 
    let(:show!) do 
    get :show 
    end 

    context do 
    before { show! } 

    context "published item" do 
     it { should redirect_to(success_url) } 
    end 

    # another examples with show-before-each 
    end 

    context "unpublished item" do 
    before do 
     item.update_attribute(published: false) 
     show! 
    end 

    it { should redirect_to(error_url) } 
    end 
end 
+0

Esto no es del todo él. Preferiría poder editar el elemento existente en lugar de reemplazarlo por completo, lo que haría esta solución. El razonamiento es que facilita la creación de contextos reutilizables que realizan cambios incrementales en el estado de configuración para que se puedan probar diferentes permutaciones al combinar esos contextos. –

+0

Eche un vistazo a otro enfoque en la respuesta actualizada. Creo que no hay otra manera de cambiar la lógica de anzuelos anidados. –

+0

Terminé usando una solución similar. Todavía se siente un poco sucio, pero funciona. Solo tiene que asegurarse de que 'show!' Se invoque correctamente en contextos anidados y asegúrese de que no termine siendo llamado dos veces. Es más sobrecarga mental de lo que quiero, pero hace el trabajo por ahora. –

Cuestiones relacionadas