Rspec Stubbing Method for Only Specific Arguments

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



Leave a reply



Submit