ruby on rails - tutorial - Pruebe el código de estado HTTP en algunos ejemplos de solicitud de rieles RSpec, pero para la excepción en otros




shoulda matchers (4)

En una aplicación Rails 4.2.0 probada con rspec-rails , proporciono una API web JSON con un recurso similar a REST con un atributo obligatorio mand_attr .

Me gustaría probar que esta API responde con el código HTTP 400 ( BAD REQUEST ) cuando falta ese atributo en una solicitud POST. (Vea el segundo ejemplo.) Mi controlador intenta causar este código HTTP al lanzar un ActionController::ParameterMissing , como se ilustra en el primer ejemplo de RSpec a continuación.

En otros ejemplos de RSpec, quiero que las excepciones generadas sean rescatadas por los ejemplos (si se esperan) o que lleguen al corredor de prueba, para que se muestren al desarrollador (si el error es inesperado), por lo tanto no deseo para eliminar

  # Raise exceptions instead of rendering exception templates.
  config.action_dispatch.show_exceptions = false

desde config/environments/test.rb

Mi plan era tener algo como lo siguiente en una especificación de solicitud :

describe 'POST' do
  let(:perform_request) { post '/my/api/my_ressource', request_body, request_header }
  let(:request_header) { { 'CONTENT_TYPE' => 'application/json' } }

  context 'without mandatory attribute' do
    let(:request_body) do
      {}.to_json
    end

    it 'raises a ParameterMissing error' do
      expect { perform_request }.to raise_error ActionController::ParameterMissing,
                                                'param is missing or the value is empty: mand_attr'
    end

    context 'in production' do
      ###############################################################
      # How do I make this work without breaking the example above? #
      ###############################################################
      it 'reports BAD REQUEST (HTTP status 400)' do
        perform_request
        expect(response).to be_a_bad_request
        # Above matcher provided by api-matchers. Expectation equivalent to
        #     expect(response.status).to eq 400
      end
    end
  end

  # Below are the examples for the happy path.
  # They're not relevant to this question, but I thought
  # I'd let you see them for context and illustration.
  context 'with mandatory attribute' do
    let(:request_body) do
      { mand_attr: 'something' }.to_json
    end

    it 'creates a ressource entry' do
      expect { perform_request }.to change(MyRessource, :count).by 1
    end

    it 'reports that a ressource entry was created (HTTP status 201)' do
      perform_request
      expect(response).to create_resource
      # Above matcher provided by api-matchers. Expectation equivalent to
      #     expect(response.status).to eq 201
    end
  end
end

He encontrado dos soluciones de trabajo y una de trabajo parcial que publicaré como respuestas. Pero no estoy particularmente contento con ninguno de ellos, así que si puedes encontrar algo mejor (o simplemente diferente), ¡me gustaría ver tu enfoque! Además, si una especificación de solicitud es del tipo incorrecto para probar esto, me gustaría saberlo.

Preveo la pregunta

¿Por qué está probando el marco de Rails en lugar de solo su aplicación Rails? ¡El framework Rails tiene sus propias pruebas!

así que permítame responderlo preventivamente: siento que no estoy probando el marco en sí mismo aquí, pero si estoy usando el marco correctamente. Mi controlador no hereda de ActionController::Base sino de ActionController::API y no sabía si ActionController::API usa ActionDispatch::ExceptionWrapper de manera predeterminada o si primero habría tenido que decirle a mi controlador que lo hiciera de alguna manera .


Devolviendo nil desde una configuración de aplicación parcialmente simulada con

    context 'in production' do
      before do
        allow(Rails.application.config.action_dispatch).to receive(:show_exceptions)
      end

      it 'reports BAD REQUEST (HTTP status 400)' do
        perform_request
        expect(response).to be_a_bad_request
      end
    end

o más explícitamente con

    context 'in production' do
      before do
        allow(Rails.application.config.action_dispatch).to receive(:show_exceptions).and_return nil
      end

      it 'reports BAD REQUEST (HTTP status 400)' do
        perform_request
        expect(response).to be_a_bad_request
      end
    end

Funcionaría si ese fuera el único ejemplo que se está ejecutando. Pero si lo fuera, podríamos descartar la configuración de config/environments/test.rb , por lo que es un poco discutible. Cuando hay varios ejemplos, esto no funcionará , ya que Rails.application.env_config() , que consulta esta configuración, almacena en caché su resultado.


En mi opinión, la prueba de excepción no pertenece a una especificación de solicitud; las especificaciones de solicitud generalmente son para probar su api desde la perspectiva del cliente para asegurarse de que toda la pila de aplicaciones funciona como se espera. También son de naturaleza similar a las pruebas de características cuando se prueba una interfaz de usuario. Entonces, debido a que sus clientes no verán esta excepción, es probable que no pertenezca allí.

También puedo simpatizar con su preocupación acerca de usar el marco correctamente y querer asegurarme de eso, pero parece que se está involucrando demasiado con el funcionamiento interno del marco.

Lo que haría sería primero averiguar si estoy usando la función en el marco correctamente, (esto se puede hacer con un enfoque TDD o como un pico); una vez que comprendo cómo lograr lo que quiero lograr, escribiría una especificación de solicitud en la que asumo el rol de cliente y no me preocupo por los detalles del marco; sólo prueba la salida dada entradas específicas.

Me interesaría ver el código que ha escrito en su controlador porque también se puede usar para determinar la estrategia de prueba. Si escribió el código que genera la excepción, entonces eso podría justificar una prueba, pero lo ideal sería que fuera una prueba unitaria para el controlador. Lo que sería una prueba de controlador en un rspec-rails .


Rails.application.env_config() para devolver un resultado modificado

    context 'in production' do
      before do
        # We don't really want to test in a production environment,
        # just in a slightly deviating test environment,
        # so use the current test environment as a starting point ...
        pseudo_production_config = Rails.application.env_config.clone

        # ... and just remove the one test-specific setting we don't want here:
        pseudo_production_config.delete 'action_dispatch.show_exceptions'

        # Then let `Rails.application.env_config()` return that modified Hash
        # for subsequent calls within this RSpec context.
        allow(Rails.application).to receive(:env_config).
                                        and_return pseudo_production_config
      end

      it 'reports BAD REQUEST (HTTP status 400)' do
        perform_request
        expect(response).to be_a_bad_request
      end
    end

Hará el truco. Tenga en cuenta que env_config() el resultado de env_config() , para que no modifiquemos el hash original que afectaría a todos los ejemplos.


    context 'in production' do
      around do |example|
        # Run examples without the setting:
        show_exceptions = Rails.application.env_config.delete 'action_dispatch.show_exceptions'
        example.run

        # Restore the setting:
        Rails.application.env_config['action_dispatch.show_exceptions'] = show_exceptions
      end

      it 'reports BAD REQUEST (HTTP status 400)' do
        perform_request
        expect(response).to be_a_bad_request
      end
    end

Hará el truco, pero se siente un poco sucio. Funciona porque Rails.application.env_config() da acceso al Hash subyacente que usa para almacenar en caché su resultado, por lo que podemos modificarlo directamente.





actiondispatch