【问题标题】:Testing with rspec .consider_all_requests_local = false使用 rspec 进行测试 .consider_all_requests_local = false
【发布时间】:2015-06-29 07:45:23
【问题描述】:

我在我的 application_controller 中使用:.consider_all_requests_locallike

  unless Rails.application.config.consider_all_requests_local
    rescue_from ActionController::InvalidCrossOriginRequest, :with => :render_404
  end

如果引发 ActionController::InvalidCrossOriginRequest,则返回 404。在本地环境中它没有被提升,它有利于调试。 对于这部分,它正在工作。但我想用 rspec 测试一下。

我尝试过类似的东西

describe 'ActionController::InvalidCrossOriginRequest render 404' do
    before { Rails.application.config.consider_all_requests_local = false }
    controller do
      def index
        raise ActionController::InvalidCrossOriginRequest
      end
    end

    subject { xhr :get, :index, format: :js }

    its(:status) { is_expected.to eq 404 }
end

两件事。我可能没有以正确的方式加注。在本地调用 mywebsite.com/editor/fckeditor.js 时会发生错误。没有找到调用特定网址的方法。

第二个问题,之前不会改变Rails.application.config.consider_all_requests_local的状态。

我明白了:

1) ApplicationController ActionController::InvalidCrossOriginRequest render 404 status
     Failure/Error: raise ActionController::InvalidCrossOriginRequest
     ActionController::InvalidCrossOriginRequest:
       ActionController::InvalidCrossOriginRequest

【问题讨论】:

    标签: ruby-on-rails ruby rspec tdd rspec-rails


    【解决方案1】:

    该问题似乎是由您在类加载时执行的unless 检查引起的。这意味着第一次加载类时,会检查应用程序配置中的值,rescue_from 设置或未设置。

    在最基本的解决方法中,您需要使用load 以在更改设置后重新读取该文件。但是,照原样,一旦打开rescue_from,再次加载文件不会导致它关闭。

    下一个替代方法是使用rescue_from(with:),它委托给助手或the block form。您可以使用此帮助器检查值并处理或不处理条件。但是,考虑到这看起来是您只想在非生产环境中执行的操作,您可以将两者结合起来。使用unless 来验证您是否不在生产环境中,然后使用 with 每次检查配置。

    类似:

    class ApplicationController < ActionController::Base
      unless Rails.env.production?
        rescue_from ActionController::InvalidCrossOriginRequest do
          unless Rails.application.config.consider_all_requests_local
            render_404
          end
        end
      end
    end
    

    【讨论】:

    • 感谢@aaron-k 非常好的回答。
    • 很抱歉对这么老的答案发表评论。但是我认为如果 Rails.application.config.consider_all_requests_local 是真的,这里有一个问题你实际上不想拯救。虽然此解决方案将跳过 404 的呈现,但如果它是真的它仍然会吞下异常?
    • 好点,@JackCasey。这是一个合理的担忧。由于异常被传递给任何rescue_from 错误处理程序方法,您可以并且应该在else 情况下重新引发异常(如果Rails.application.config.consider_all_requests_local 为真)。我以我最终这样做的方式发布了答案......
    • 我建议不要检查consider_all_requests_local,而是检查action_dispatch.show_detailed_exceptions,就像Rails 在ActionDispatch::DebugExceptions 中所做的那样,正如atodorov.org/blog/2016/04/27/… 中所建议的那样。这是一个更直接的标志,用于检查它是否应该呈现开发/诊断“详细”异常错误页面。我添加了一个简单的show_detailed_exceptions? 方法以使其更易于检查(请参阅下面的答案)。
    【解决方案2】:

    尝试模拟它而不是设置:

    before { Rails.stub_chain('application.config.consider_all_requests_local').and_return(false) }
    

    更多信息here

    此语法已弃用,因此您可以关闭弃用警告或使用新的“解决方法”

    allow(object).to receive_message_chain(:one, :two, :three).and_return(:four)
    expect(object.one.two.three).to eq(:four)
    

    here发布的那样

    【讨论】:

    • 感谢您的分析器 Piotr,但不推荐使用此语法:来自 rspec-mocks 的旧 :should 语法的 Failure/Error: before { Rails.stub_chain('application.config.consider_all_requests_local').and_return(false) } Using stub_chain` 没有明确启用该语法已被弃用。改用新的:expect 语法或显式启用:should。`
    【解决方案3】:

    我曾经在我的 rescue_from 配置周围也有一个守卫,例如:

    unless Rails.application.config.consider_all_requests_local
      rescue_from Exception, with: :render_error
      …
    end
    

    ... 效果很好,直到我试图弄清楚如何让它处理错误并在一些 测试 中显示漂亮的自定义错误页面(就像它在生产中所做的那样)。 @Aaron K 的 answer 有助于解释为什么无法在类定义中评估检查,而必须在实际错误处理程序中(在运行时)进行检查。但这只是为我解决了部分问题。

    这就是我所做的......

    ApplicationController 中,如果show_detailed_exceptions 标志(比consider_all_requests_local 更合适的检查)为真,请记住重新引发任何错误。换句话说,仅当应用程序/请求配置为处理生产错误时才进行生产错误处理;否则“通过”并重新引发错误。

      rescue_from Exception,                           with: :render_error
      rescue_from ActiveRecord::RecordNotFound,        with: :render_not_found
      rescue_from ActionController::RoutingError,      with: :render_not_found
      rescue_from AbstractController::ActionNotFound,  with: :render_not_found
    
      def show_detailed_exceptions?
        # Rails.application.config.consider_all_requests_local causes this to be set to true as well.
        request.get_header("action_dispatch.show_detailed_exceptions")
      end
    
      def render_not_found(exception = nil, template = 'errors/not_found')
        raise exception if show_detailed_exceptions?
        logger.error exception if exception
        render template, formats: [:html], status: :not_found
      end
    
      def render_error(exception)
        raise exception if show_detailed_exceptions?
        deliver_exception_notification(exception)
        logger.error exception
    
        # Prevent AbstractController::DoubleRenderError in case we've already rendered something
        method(:response_body=).super_method.call(nil)
    
        respond_to do |format|
          format.html { render 'errors/internal_server_error', formats: [:html], status: :internal_server_error }
          format.any  { raise exception }
        end
      end
    

    添加到spec/support/handle_exceptions_like_production.rb:

    shared_context 'handle_exceptions_like_production', handle_exceptions_like_production: true do
      before do |example|
        case example.metadata[:type]
        when :feature
          method = Rails.application.method(:env_config)
          allow(Rails.application).to receive(:env_config).with(no_args) do
            method.call.merge(
              'action_dispatch.show_exceptions' => true,
              'action_dispatch.show_detailed_exceptions' => false,
              'consider_all_requests_local' => true
            )
          end
        when :controller
          # In controller tests, we can only test *controller* behavior, not middleware behavior.  We
          # can disable show_detailed_exceptions here but we can *only* test any behaviors that depend
          # on it that are defined in our *controller* (ApplicationController). Because the request
          # doesn't go through the middleware (DebugExceptions, ShowExceptions) — which is what actually
          # renders the production error pages — in controller tests, we may not see the exact same
          # behavior as we would in production. Feature (end-to-end) tests may be needed to more
          # accurately simulate a full production stack with middlewares.
          request.set_header 'action_dispatch.show_detailed_exceptions', false
        else
          raise "expected example.metadata[:type] to be one of :feature or :controller but was #{example.metadata[:type]}"
        end
      end
    end
    
    RSpec.configure do |config|
      config.include_context 'handle_exceptions_like_production', :handle_exceptions_like_production
    end
    

    然后,在端到端(功能)测试中,您希望它像在生产中那样处理异常(换句话说,将其视为本地请求),只需将:handle_exceptions_like_production 添加到您的示例组:

    describe 'something', :handle_exceptions_like_production do
      it …
    end
    

    例如:

    spec/features/exception_handling_spec.rb:

    describe 'exception handling', js: false do
      context 'default behavior' do
        it do |example|
          expect(example.metadata[:handle_exceptions_like_production]).to eq nil
        end
    
        describe 'ActiveRecord::RecordNotFound' do
          it do
            expect {
              visit '/users/0'
            }.to raise_exception(ActiveRecord::RecordNotFound)
          end
        end
    
        describe 'ActionController::RoutingError' do
          it do
            expect {
              visit '/advertisers/that_track_you_and_show_you_personalized_ads/'
            }.to raise_exception(ActionController::RoutingError)
          end
        end
    
        describe 'RuntimeError => raised' do
          it do
            expect {
              visit '/test/exception'
            }.to raise_exception(RuntimeError, 'A test exception')
          end
        end
      end
    
      context 'when :handle_exceptions_like_production is true', :handle_exceptions_like_production do
        describe 'ActiveRecord::RecordNotFound => production not_found page' do
          it do
            expect {
              visit '/users/0'
            }.to_not raise_exception
            expect_not_found
          end
        end
    
        describe 'ActionController::RoutingError => production not_found page' do
          it do
            visit '/advertisers/that_track_you_and_show_you_personalized_ads/'
            expect_not_found
          end
        end
    
        describe 'RuntimeError => production not_found page' do
          it do
            visit '/test/exception'
            expect_application_error
          end
        end
      end
    end
    

    它也可以用于控制器测试——如果您在 ApplicationController 中定义了生产错误处理。 spec/controllers/exception_handling_spec.rb:

    describe 'exception handling' do
      context 'default behavior' do
        describe UsersController do
          it do
            expect {
              get 'show', params: {id: 0}
            }.to raise_exception(ActiveRecord::RecordNotFound)
          end
        end
    
        describe TestController do
          it do
            expect {
              get 'exception'
            }.to raise_exception(RuntimeError, 'A test exception')
          end
        end
      end
    
      context 'when handle_exceptions_like_production: true', :handle_exceptions_like_production do
        describe UsersController do
          it do
            expect {
              get 'show', params: {id: 0}
            }.to_not raise_exception
            expect(response).to render_template('errors/not_found')
          end
        end
    
        describe TestController do
          it do
            expect {
              get 'exception'
            }.to_not raise_exception
            expect(response).to render_template('errors/internal_server_error')
          end
        end
      end
    end
    

    测试:rspec 3.9rails 5.2

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-06-30
      • 2017-07-22
      • 2019-03-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多