【问题标题】:How to get coverage when using rescue, and retry using stubs not VCR, in Rspec 3.5?在 Rspec 3.5 中,如何在使用救援时获得覆盖,并使用存根而不是 VCR 重试?
【发布时间】:2017-05-26 13:09:00
【问题描述】:

我正在尝试覆盖我附加规范中的以下代码部分。这个项目更喜欢在 VCR 上使用存根,并且外部 API 请求被阻止。是否有人对如何解决此问题以获得所需的报道有任何想法?

我已将睡眠测试注释掉,因为它失败了。

代码

  def first_page
    client.list_orders(created_before: created_before,
                       created_after: created_after,
                       max_results_per_page: max_results_per_page)
  rescue Excon::Error::ServiceUnavailable => error
    log_error(error, 'amazon_mws.errors.orders.first_page') unless (self.retries -= 1).positive?
    add_api_delay
    retry
  end

  def next_page(next_token)
    client.list_orders_by_next_token(next_token)
  rescue Excon::Error::ServiceUnavailable => error
    log_error(error, 'amazon_mws.errors.orders.next_page') unless (self.retries -= 1).positive?
    add_api_delay
    retry
  end

  def add_api_delay
    sleep(Configurations.amazon_mws['orders']['response_timer_in_seconds'])
  end

规格

  describe '.poll_for_order_items' do
    context 'multiple pages' do
      before do
        stub_request(:any, /.*amazonservices.com.*/)
          .with(body: /^.*(&Action=ListOrderItems&).*$/)
          .to_return(body: next_page, status: 200, headers: { 'Content-Type': 'text/xml' })

        stub_request(:any, /.*amazonservices.com.*/)
          .with(body: /^.*(&Action=ListOrderItemsByNextToken&).*$/)
          .to_return(body: first_page, status: 200, headers: { 'Content-Type': 'text/xml' })
      end

      context 'with sleep stubbed' do
        before do
          allow(subject).to receive(:add_api_delay).and_return(true)
        end

        it 'returns array collection' do
          expect(subject.poll_for_order_items).to be_kind_of(Array)
        end

        it 'returns array data' do
          expect(subject.poll_for_order_items.length).to be > 0
        end
      end

      # context 'with sleep set' do
      #   it 'should call sleep' do
      #     allow(Kernel).to receive(:sleep).and_return(1)
      #     expect(subject).to receive(:sleep).and_return(1)
      #     subject.poll_for_order_items
      #   end
      # end
    end

    context 'single page' do
      before(:each) do
        stub_request(:any, /.*amazonservices.com.*/).to_return(body: first_page, status: 200, headers: { 'Content-Type': 'text/xml' })
        allow(subject).to receive(:add_api_delay).and_return(true)
      end

      it 'returns array' do
        expect(subject.poll_for_order_items).to be_kind_of(Array)
      end

      it 'returns array data' do
        expect(subject.poll_for_order_items.length).to be > 0
      end
    end

    context 'collector' do
      before(:each) do
        stub_request(:any, /.*amazonservices.com.*/).to_return(body: first_page_single, status: 200, headers: { 'Content-Type': 'text/xml' })
        allow(subject).to receive(:add_api_delay).and_return(true)
      end

      it 'returns array when non array returned' do
        expect(subject.poll_for_order_items).to be_kind_of(Array)
      end
    end
  end

  describe 'raises errors' do
    context '.parse' do
      let(:error_raised) { I18n.t('amazon_mws.errors.parse_payload') }

      before do
        stub_request(:any, /.*amazonservices.com.*/)
          .with(body: /^.*(&Action=ListOrderItems&).*$/)
          .to_return(body: {}.to_json, status: 200, headers: { 'Content-Type': 'text/xml' })
      end

      it 'raises error' do
        expect { subject.new } .to raise_error(StandardError)
      end
    end

    context '.first_page' do
      let(:error_raised) { I18n.t('amazon_mws.errors.order_items.first_page') }

      before do
        allow(MWS).to receive_message_chain(:orders, :list_order_items).and_raise(error_raised)
      end

      it 'raises error on first listing page' do
        expect { subject.new } .to raise_error(StandardError)
      end
    end

    context '.next_page' do
      let(:error_raised) { I18n.t('amazon_mws.errors.order_items.next_page') }

      before do
        stub_request(:any, /.*amazonservices.com.*/)
          .with(body: /^.*(&Action=ListOrderItems&).*$/)
          .to_return(body: next_page, status: 200, headers: { 'Content-Type': 'text/xml' })

        stub_request(:any, /.*amazonservices.com.*/)
          .with(body: /^.*(&Action=ListOrderItemsByNextToken&).*$/)
          .and_raise(error_raised)

        allow(subject).to receive(:add_api_delay).and_return(true)
      end

      it 'raises error on nexst listing page' do
        expect { subject.poll_for_order_items } .to raise_error(StandardError)
      end
    end
  end

覆盖率报告

【问题讨论】:

    标签: ruby-on-rails rspec rescue test-coverage retry-logic


    【解决方案1】:

    我只看到您希望它会引发错误。如果超时,那么它应该至少调用一次log_erroradd_api_delay。由于这些基本上是副作用,您可能只需要测试它们在这种情况下被调用并且覆盖率报告应该知道它。

    尝试添加期望,例如:expect(subject).to have_received(:log_error).at_least(:once)

    编辑:在带外谈论它,我们了解到大多数覆盖问题可以通过确保引发错误来解决。

    before do stub_request(:any, /.*amazonservices.com.*/) .with(body: /^.*(&Action=ListOrders&).*$/) .to_raise(Excon::Error::ServiceUnavailable) end

    【讨论】:

      【解决方案2】:

      经过上述@wobh 的研究和启发,我想出了以下...

        describe '.poll_for_order_items' do
          context 'multiple pages' do
            before do
              stub_request(:any, /.*amazonservices.com.*/)
                .with(body: /^.*(&Action=ListOrderItems&).*$/)
                .to_return(body: next_page, status: 200, headers: { 'Content-Type': 'text/xml' })
      
              stub_request(:any, /.*amazonservices.com.*/)
                .with(body: /^.*(&Action=ListOrderItemsByNextToken&).*$/)
                .to_return(body: first_page, status: 200, headers: { 'Content-Type': 'text/xml' })
            end
      
            context 'with sleep stubbed' do
              before do
                allow(subject).to receive(:add_api_delay).and_return(true)
              end
      
              it 'returns array collection' do
                expect(subject.poll_for_order_items).to be_kind_of(Array)
              end
      
              it 'returns array data' do
                expect(subject.poll_for_order_items.length).to be > 0
              end
            end
          end
      
          context 'single page' do
            before(:each) do
              stub_request(:any, /.*amazonservices.com.*/).to_return(body: first_page, status: 200, headers: { 'Content-Type': 'text/xml' })
              allow(subject).to receive(:add_api_delay).and_return(true)
            end
      
            it 'returns array' do
              expect(subject.poll_for_order_items).to be_kind_of(Array)
            end
      
            it 'returns array data' do
              expect(subject.poll_for_order_items.length).to be > 0
            end
          end
      
          context 'collector' do
            before(:each) do
              stub_request(:any, /.*amazonservices.com.*/).to_return(body: first_page_single, status: 200, headers: { 'Content-Type': 'text/xml' })
              allow(subject).to receive(:add_api_delay).and_return(true)
            end
      
            it 'returns array when non array returned' do
              expect(subject.poll_for_order_items).to be_kind_of(Array)
            end
          end
        end
      
        describe 'raises errors' do
          context '.parse' do
            let(:error_raised) { I18n.t('amazon_mws.errors.parse_payload', error: 'TEST') }
      
            before do
              stub_request(:any, /.*amazonservices.com.*/)
                .with(body: /^.*(&Action=ListOrderItems&).*$/)
                .to_return(body: {}.to_json, status: 200, headers: { 'Content-Type': 'text/xml' })
            end
      
            it 'raises error' do
              expect { subject.poll_for_order_items } .to raise_error(StandardError)
            end
      
            it 'raises correct error message' do
              expect { subject.poll_for_order_items } .to raise_error(/payload/)
            end
          end
      
          context '.first_page' do
            let(:error_raised) { I18n.t('amazon_mws.errors.order_items.first_page', error: 'TEST') }
      
            before do
              stub_request(:any, /.*amazonservices.com.*/)
                .with(body: /^.*(&Action=ListOrderItems&).*$/)
                .to_raise(Excon::Error::ServiceUnavailable)
              subject.instance_variable_set(:@retry_limit, 1)
              allow(subject).to receive(:add_api_delay).and_return(true)
            end
      
            it 'raises error on first listing page' do
              expect { subject.poll_for_order_items } .to raise_error(StandardError)
            end
      
            it 'raises correct error message' do
              expect { subject.poll_for_order_items } .to raise_error(/order item first/)
            end
          end
      
          context '.next_page' do
            let(:error_raised) { I18n.t('amazon_mws.errors.order_items.next_page', error: 'TEST') }
      
            before do
              stub_request(:any, /.*amazonservices.com.*/)
                .with(body: /^.*(&Action=ListOrderItems&).*$/)
                .to_return(body: next_page, status: 200, headers: { 'Content-Type': 'text/xml' })
      
              stub_request(:any, /.*amazonservices.com.*/)
                .with(body: /^.*(&Action=ListOrderItemsByNextToken&).*$/)
                .to_raise(Excon::Error::ServiceUnavailable)
              subject.instance_variable_set(:@retry_limit, 1)
              allow(subject).to receive(:add_api_delay).and_return(true)
            end
      
            it 'raises error on next listing page' do
              expect { subject.poll_for_order_items } .to raise_error(StandardError)
            end
      
            it 'raises correct error message' do
              expect { subject.poll_for_order_items } .to raise_error(/order item next/)
            end
          end
      
          context '.add_api_delay' do
            before do
              stub_request(:any, /.*amazonservices.com.*/)
                .with(body: /^.*(&Action=ListOrderItems&).*$/)
                .to_raise(Excon::Error::ServiceUnavailable)
      
              subject.instance_variable_set(:@retries, 2)
              subject.instance_variable_set(:@retry_limit, 1)
              subject.instance_variable_set(:@retry_delay, 1)
            end
      
            it 'sleeps retry api calls' do
              allow(Kernel).to receive(:sleep).and_return(1)
              expect(subject).to receive(:sleep).and_return(1)
              expect { subject.poll_for_order_items } .to raise_error(StandardError)
            end
          end
        end
      

      这实现了 100% 的覆盖率!

      【讨论】:

      • 只是一个建议:如果您可以删除与retry无关的部分,答案会更清楚
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多