Search code examples
ruby-on-railsrspecrescuetest-coverageretry-logic

How to get coverage when using rescue, and retry using stubs not VCR, in Rspec 3.5?


I am trying to get coverage on the following sections of code in my attached spec. This project prefers to use stubs over VCR and external API requests are blocked. Does anyone have any ideas on how to approach this issue to get the coverage needed?

I have the sleep test commented out because it is failing.

Code

  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

Spec

  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

Coverage Report enter image description here


Solution

  • After research and inspiration from @wobh above I came up with the following...

      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
    

    THIS RESULTED IN 100% COVERAGE!