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
"No Available Formula for Gcc46" While Installing Ruby 1.9.3 on Os X with Rvm:
Gmail Threading, Imap and Ruby
Emacs Ruby Method Parameter Indentation
Using Rvm, But Can't Set Current Ruby Version (Ubuntu 11.10)
Rails Elasticsearch Aggregation
Best Practice for Limiting the Number of Associations Within a Has_Many Relationship
Bundle Install Not Running from My Post-Update Hook
Form_For with Multiple Controller Actions for Submit
Ruby JSON.Pretty_Generate ... Is Pretty Unpretty
Compute Geo Distance in Elasticsearch
Converting Video to Flash and Mp4 Ruby on Rails
How to Log Specific Request Details to Rails Server Logs
Why Is Foreman Gem Ignoring the Port Environment Variable
Ruby on Rails Collection Select - How to Pre-Select the Right Value
Linux Cli: How to Render Arabic Text into Bitmap
Solid Tutorial for Building a Simple Wiki Application in Ruby on Rails