Stub Method Only on The First Call with Rspec

stub method only on the first call with Rspec

You can pass a block to a stub that will be invoked when the stub is called. You can then perform the unstub in there, in addition to doing whatever you need to.

class Foo
def initialize
@calls = 0
end

def be_persistent
begin
increment
rescue
retry
end
end

def increment
@calls += 1
end
end

describe "Stub once" do
let(:f) { Foo.new }
before {
f.stub(:increment) { f.unstub(:increment); raise "boom" }
}

it "should only stub once" do
f.be_persistent.should == 1
end
end

Seems to work nicely here.

$ rspec stub.rb -f doc

Stub once
should only stub once

Finished in 0.00058 seconds
1 example, 0 failures

Alternately, you could just track the number of calls and return different results for the stub based on the call count:

describe "Stub once" do
let(:f) { Foo.new }

it "should return different things when re-called" do
call_count = 0
f.should_receive(:increment).twice {
if (call_count += 1) == 1
raise "boom"
else
"success!"
end
}

f.be_persistent.should == "success!"
end
end

RSpec: How to Stub Only One of Multiple Calls to Same Method

There is a and_call_original option (see rspec docs).

Applied to your example, this should do what you are looking for:

before do
allow(YAML).to receive(:load_file).and_call_original
allow(YAML).to receive(:load_file).with('b.txt').and_return('b_data')
end

Mock a method only once

It's perhaps a bit unintuitive, but you can do this by specifying different return values for multiple calls of Klass.meth.

In your case, you can stub the first call to Klass.meth with 'foo', and then stub every other call to Klass.meth with the original implementation of the method. That looks like this:

allow(described_class).to receive(:meth).and_return('foo', described_class.meth)

The next thing we'd need to change in your test is to not use subject in the final test, because it is memoising the value returned when Klass.meth gets called the first time (which is why all the other tests that use subject will still pass), and hence making the second expectation in the it 'only mocks once' test fail. Instead, we can just call the method directly in each spec:

class Klass
def self.meth
'baz'
end
end

describe Klass do
subject { described_class.meth }

before do
allow(described_class).to \
receive(:meth).and_return('foo', described_class.meth)
end

it { is_expected.to eq 'foo' }

context 'throwing in a context just to test' do
it { is_expected.to eq 'foo' }
it { is_expected.to eq 'foo' }
it { is_expected.to eq 'foo' }

it 'only mocks once' do
expect(described_class.meth).to eq 'foo'
expect(described_class.meth).to eq 'baz'
end # pass
end
end

how to stub a method for a specific argument in Rspec

When stubbing methods with specific arguments, you are only stubbing that specific method call with those specific parameters. All other calls to the method will fail with the error:

 #<Foo (class)> received :bar with unexpected arguments

As the OP discovered, the solution here is to first stub ALL calls to the object with the and_call_through method, then stub the specific calls with the specific arguments you wish to stub.

From the OP's answer, the first line stubs ALL calls to the Flipper object and allows them to call through to the underlying code, and the second line stubs the call that receives :premium_plus_features_beta and returns false:

allow(Flipper).to receive(:enabled?).and_call_original
allow(Flipper).to receive(:enabled?).with(:beta).and_return(false)

Also, there's one other point to be made here. The code in the OP question used the OLD RSpec expectation syntax. The code in the OP answer uses the NEW RSpec stub syntax. So, when the code said this:

Flipper.should_receive(:enabled?).and_call_original
Flipper.should_receive(:enabled?).with(:beta).and_return(false)

What it was doing was this:

expect(Flipper).to have_received(:enabled?).and_call_original
expect(Flipper).to have_received(:enabled?).with(:beta).and_return(false)

Which is entirely different than what I think the OP was really trying to do:

before do
allow(Flipper).to receive(:enabled?).and_call_original
allow(Flipper).to receive(:enabled?).with(:beta).and_return(enabled?)
end

context "when the beta is disabled" do
let(:enabled?) { false }

it "hides the beta" do
...
end
end

context "when the beta is enabled" do
let(:enabled?) { true }

it "shows the beta" do
...
end
end

Finally, for those who are curious about why RSpec changed the syntax... The old syntax required a monkey patch on Object in order to add the should_receive method. I think the RSpec team preferred the new syntax because it no longer required the monkey patch.

Ruby Rspec Mock Same instance to behave different in the first and second call

Yes, you can tell RSpec mocks to return different values on each call. The following example would return false on the first call and true on all later calls:

allow(@verify).to receive(:sucess?).and_return(false, true)

How to integrate that into your test depend on how you set @verify and how your tests look in general.

Why is the stub only being used in the first test?

It was because the class under test was a singleton. I followed the guidance found here (Resetting a singleton instance in Ruby) to unit test a singleton.

RSpec stubbing only works for first request, does not work for subsequent requests

Finally discovered the problem.

In test.rb, config.cache_classes was set to false (no idea why since it defaults to true).

You want config.cache_classes to be set to true in testing environments, otherwise classes will be reloaded on every request and its methods will override the stub.

Stubbing a method call that accepts a block in rspec

If using rspec-mocks, you can stub method calls to MyGem.search with a partial test double.

A partial test double is an extension of a real object in a system that is instrumented with
test-double like behaviour in the context of a test. This technique is very common in Ruby
because we often see class objects acting as global namespaces for methods.

To make a partial double of MyGem and expect a call to its search class method:

expect(MyGem).to receive(:search)

Then you can add additional behavior on this double to check the call arguments, make it yield values, or raise an error:

# match arguments
expect(MyGem).to receive(:search).with(:attributes => ["displayname", "employeeID"])
# yield value
expect(MyGem).to receive(:search).and_yield(42)
# yield multiple values
expect(MyGem).to receive(:search).and_yield(42).and_yield(987)
# raise an error
expect(MyGem).to receive(:search).and_raise(ArgumentError, "bad request")
# all at once!
expect(MyGem).to receive(:search).with(:attributes => ["displayname", "employeeID"])
.and_yield(42)
.and_raise(ArgumentError, "bad request")

Based on your information (which seems incomplete: dept argument is never used...), here is a fully working example:

Entry = Struct.new(:employeeid, :displayname)

class MyGem
def self.search(*args)
yield Entry.new([1, 2, 3], ["one", "two", "three"])
end
end

class Organization
def search(*args, &blk)
MyGem.search(*args, &blk)
end

def members_of(dept)
result = {}
search(:attributes => ["displayname", "employeeID"]) do |entry|
result[entry.employeeid.first.to_i] = entry.displayname.first.to_s
end
result
rescue
[]
end
end

RSpec.describe Organization do
describe "#members_of" do
it "calls search" do
organization = Organization.new
expect(MyGem).to receive(:search).with(:attributes => ["displayname", "employeeID"])
.and_yield(Entry.new([1337, 100, 2000], ["leet", "one hundred", "two thousand"]))
.and_yield(Entry.new([123], ["one-two-three"]))
members = organization.members_of("dept")
expect(members).to match({
1337 => "leet",
123 => "one-two-three"})
end
it "returns empty array on search error" do
organization = Organization.new
expect(MyGem).to receive(:search).and_raise(ArgumentError)
expect(organization.members_of("dept")).to eq([])
end
end
end

Also, when using partial doubles, you should enable the verify_partial_doubles config option to ensure you are not stubbing class methods that do not exist. Enable it with this snippet in your spec/spec_helper.rb file:

RSpec.configure do |config|
config.mock_with :rspec do |mocks|
mocks.verify_partial_doubles = true
end
end

RSpec in simple class with an error: please stub a default value first

The actual code is passing FetchPending::ProjectKey as the argument to Jira::ProjectConnection.new.

In your test you are defining a variable:

let(:project_key) { 'TSW-123' }

but this isn't actually getting used anywhere.

A simple fix would be to change

allow(Jira::ProjectConnection).to receive(:new).with('TSW-123').and_return(project)

with

allow(Jira::ProjectConnection).to receive(:new).with(described_class::PROJECT_KEY).and_return(project)


Related Topics



Leave a reply



Submit