RSpec retry throw exception and then return value

I have a retry block

 def my_method
    app_instances = []
    attempts = 0
    begin 
      app_instances = fetch_and_rescan_app_instances(page_n, policy_id, policy_cpath)
    rescue Exception
      attempts += 1
      retry unless attempts > 2
      raise Exception 
    end
    page_n += 1
  end

where fetch_and_rescan_app_instances access the network so can throw an exception.

I want to write an rspec test that it throws an exception first time and doesn't throw an exception second time it gets called, so I can test if the second time it doesn't throw an exception, the my_method won't throw an exeption.

I know i can do stub(:fetch_and_rescan_app_instances).and_return(1,3) and first time it returns 1 and second time 3, but I don't know how to do throw an exception first time and return something second time.


You can calculate the return value in a block:

describe "my_method" do
  before do
    my_instance = ...
    @times_called = 0
    my_instance.stub(:fetch_and_rescan_app_instances).and_return do
      @times_called += 1
      raise Exception if @times_called == 1
    end
  end

  it "raises exception first time method is called" do
    my_instance.my_method().should raise_exception
  end

  it "does not raise an exception the second time method is called" do
    begin
      my_instance.my_method()
    rescue Exception
    end
    my_instance.my_method().should_not raise_exception
  end
end

Note that you should really not be rescuing from Exception , use something more specific. See: Why is it a bad style to `rescue Exception => e` in Ruby?


What you do is constrain the times the message should be received (receive counts), ie in your case you can

instance.stub(:fetch_and_rescan_app_instances).once.and_raise(RuntimeError, 'fail')
instance.stub(:fetch_and_rescan_app_instances).once.and_return('some return value')

Calling instance.fetch_and_rescan_app_instances first time will raise RuntimeError, and second time will return 'some return value'.

PS. Calling more than that will result in an error, you might consider using different receive count specification https://www.relishapp.com/rspec/rspec-mocks/docs/message-expectations/receive-counts


This has changed a little in RSpec3.x. It seems the best approach is to pass a block to the receive that defines this type of behaviour.

The following is from the docs suggesting how to create this type of transit failure:

(This errors every other time it is called... But is easy to adapt.)

RSpec.describe "An HTTP API client" do
  it "can simulate transient network failures" do
    client = double("MyHTTPClient")

    call_count = 0
    allow(client).to receive(:fetch_data) do
      call_count += 1
      call_count.odd? ? raise("timeout") : { :count => 15 }
    end

    expect { client.fetch_data }.to raise_error("timeout")
    expect(client.fetch_data).to eq(:count => 15)
    expect { client.fetch_data }.to raise_error("timeout")
    expect(client.fetch_data).to eq(:count => 15)
  end
end
链接地址: http://www.djcxy.com/p/25854.html

上一篇: 在Ruby中自动记录异常

下一篇: RSpec重试抛出异常,然后返回值