在 Rspec 3.5 中,如何在使用救援时获得覆盖,并使用存根而不是 VCR 重试?
How to get coverage when using rescue, and retry using stubs not VCR, in Rspec 3.5?
我正试图在我的附加规范中涵盖以下代码部分。这个项目更喜欢使用存根而不是 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
覆盖率报告
我只看到您希望它引发错误。如果它超时,那么它应该至少调用一次 log_error
和 add_api_delay
。由于这些基本上是副作用,您可能只需要测试它们在那种情况下会被调用,覆盖率报告应该知道这一点。
尝试添加如下期望值:expect(subject).to have_received(:log_error).at_least(:once)
编辑:谈论它 out-of-band 我们了解到大部分覆盖问题可以通过确保引发错误来解决。
before do
stub_request(:any, /.*amazonservices.com.*/)
.with(body: /^.*(&Action=ListOrders&).*$/)
.to_raise(Excon::Error::ServiceUnavailable)
end
经过以上@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% 的覆盖率!
我正试图在我的附加规范中涵盖以下代码部分。这个项目更喜欢使用存根而不是 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
覆盖率报告
我只看到您希望它引发错误。如果它超时,那么它应该至少调用一次 log_error
和 add_api_delay
。由于这些基本上是副作用,您可能只需要测试它们在那种情况下会被调用,覆盖率报告应该知道这一点。
尝试添加如下期望值:expect(subject).to have_received(:log_error).at_least(:once)
编辑:谈论它 out-of-band 我们了解到大部分覆盖问题可以通过确保引发错误来解决。
before do
stub_request(:any, /.*amazonservices.com.*/)
.with(body: /^.*(&Action=ListOrders&).*$/)
.to_raise(Excon::Error::ServiceUnavailable)
end
经过以上@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% 的覆盖率!