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.
Rspec stubbing method for only specific arguments
You can add a default stub for the fire!
method which will call original implementation:
boss.stub(:fire!).and_call_original
boss.stub(:fire!).with(employee1).and_return(true)
Rspec 3 Syntax (@pk-nb)
allow(boss).to receive(:fire!).and_call_original
allow(boss).to receive(:fire!).with(employee1).and_return(true)
Mocha: stubbing method with specific parameter but not for other parameters
I spent an hour today trying and failing to get Mocha to allow me to only stub a particular session variable, the way that Rspec allows with ease. While I couldn't figure out a way to make this work, I did figure out a hackish workaround that might help some people, depending on the circumstances.
My "solution" was to remove the session stub after the target instance variable had been grabbed:
ActionDispatch::Request::Session.any_instance.stubs(:delete).returns(state).then.returns(nonce).then.with do |sym|
ActionDispatch::Request::Session.any_instance.unstub(:delete) if sym == :login_nonce
true
end
The trick I'm using here is, by knowing the arguments that will be passed to session.delete
in the first two calls made for a particular action, I can remove the stub after that second delete
call (for login_nonce
) has been made, so the session begins behaving like normal again.
Another potentially useful aspect of constructing a with
block like this is that the block has the full context of the caller, so one can directly inspect or extract session contents within the block. That is, if you wanted a test to grab the value of the blah
session key, you should be able to write something like
ActionDispatch::Request::Session.any_instance.stubs(:[]).with do |key|
@blah = session[key] if key == :blah
true
end
As best I can tell, the with
block always has to return true, otherwise Mocha will throw an Minitest::Assertion: unexpected invocation
exception, because it doesn't know what to do if it has stubbed a method but the argument passed in doesn't match an argument that it can handle. The fundamental problem seems to be that once one calls stubs
on any_instance
, you can no longer have Mocha return a value from the actual session
instance (unlike Rspec, which allows falling back to the original object using and_call_original
as in the linked answer above).
Hopefully someone can use build upon some of these ideas to fashion a more elegant answer in the future, but since almost 8 years have passed and there are no answers, I figure this might be a usable starting point.
RSpec - trying to stub a method that returns its own argument
You can pass a block:
allow(taxi_driver).to receive(:send_text).with(kind_of(String)){|string| string }
expect(taxi_driver.send_text("123")).to eq("123")
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
RSpec: Stub chains with arguments?
You can use this:
Payment.stub_chain(:order, :where).with(:updated_at).with(:paid => true) { return_this }
Rspec 3.0 How to mock a method replacing the parameter but with no return value?
For your example, since you don't need to test the actual result of test_method
, only that puts
gets called in it passing in param
, I would just test by setting up the expectation and running the method:
class Test
def test_method(param)
puts param
end
end
describe Test do
let(:test) { Test.new }
it 'says hello via expectation' do
expect(test).to receive(:puts).with('hello')
test.test_method('hello')
end
it 'says goodbye via expectation' do
expect(test).to receive(:puts).with('goodbye')
test.test_method('goodbye')
end
end
What it seems you're attempting to do is set up a test spy on the method, but then I think you're setting up the method stub one level too high (on test_method
itself instead of the call to puts
inside test_method
). If you put the stub on the call to puts
, your tests should pass:
describe Test do
let(:test) { Test.new }
it 'says hello using a test spy' do
allow(test).to receive(:puts).with('hello')
test.test_method('hello')
expect(test).to have_received(:puts).with('hello')
end
it 'says goodbye using a test spy' do
allow(test).to receive(:puts).with('goodbye')
test.test_method('goodbye')
expect(test).to have_received(:puts).with('goodbye')
end
end
RSpec Stubbing: Return the parameter
Note: The stub method has been deprecated. Please see this answer for the modern way to do this.
stub!
can accept a block. The block receives the parameters; the return value of the block is the return value of the stub:
class Interface
end
describe Interface do
it "should have a stub that returns its argument" do
interface = Interface.new
interface.stub!(:get_trace) do |arg|
arg
end
interface.get_trace(123).should eql 123
end
end
Related Topics
Ruby Get Time in Given Timezone
How to Convert a JSON String to an Object
How to Look Up Elevation Data by Lat/Lng
Why Is Date.Today - 6.Months + 6.Months != Date.Today
Testing a Concern/Module That Uses Activerecord
How to Configure Mongomapper and Activerecord in Same Ruby Rails Project
How to Change the Zone Offset for a Time in Ruby on Rails
Convert a String Date Format from "17-Nov-2011" to "11/17/11"
Routing Error - Uninitialized Constant
Understanding Ruby Symbol as Method Call
Round a Ruby Float Up or Down to the Nearest 0.05
Active Admin Scopes for Each Instance of a Related Model
How to Alias a Class Method in Rails Model
Cannot Load Such File - Rubygems.Rb
How to Save Dates in Local Timezone to Db with Rails3
Rails: Pg::Insufficientprivilege: Error: Permission Denied for Relation Schema_Migrations