I'm trying to stub a method like so:
allow(Flipper).to receive(:enabled?).with(:premium_plus_features_beta).and_return(false)
but when it hits a different argument - it gives me an error like this:
#<Flipper (class)> received :enabled? with unexpected arguments
expected: (:premium_plus_features_beta)
got: (:non_advertiser_profile_amp, {:lawyer_id=>4469860})
Diff:
## -1,2 +1,2 ##
-[:premium_plus_features_beta]
+[:non_advertiser_profile_amp, {:lawyer_id=>4469860}]
I normally don't stub this much but why when I explicitly tell it the arguments, why is it erroring on different arguments? They are obviously not the same. Is this just some syntax issue?
edit 1
I tried this but doesn't work
https://makandracards.com/makandra/30543-rspec-only-stub-a-method-when-a-particular-argument-is-passed
Flipper.should_receive(:enabled?).and_call_original
Flipper.should_receive(:enabled?).with(:premium_plus_features_beta).and_return(false)
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.
The working answer is:
allow(Flipper).to receive(:enabled?).and_call_original
allow(Flipper).to receive(:enabled?).with(:premium_plus_features_beta).and_return(false)
a bunch of bad info on the internet lol
Related
We have a RoR application, Rspec for tests with Webmock for HTTP requests.
After having to do some refactoring in our legacy codebase, I realized that many of our tests had unnecessary stubs.
Like this example, the do_a function has been refactored so that we don't do any api call so the stub_request is not necessary anymore, worse, it should be removed.
it 'does something' do
stub_request(:get, 'http://something.com/users/123')
do_a
expect(..)
end
One way of fixing this is:
it 'does something' do
stub_something = stub_request(:get, 'http://something.com/users/123')
do_a
expect(..)
expect(stub_something).to have_been_requested.once
end
But I'd like to enforce this directly through a strict mode where the test fails if any declared stub has not been called ? The first example would then fail automatically.
Thanks a lot for your help
You want to use expectations instead of stub_request:
expect(WebMock).to have_requested(:get, "http://something.com/users/123").once
# or
expect(a_request(:get, "http://something.com/users/123")).to have_been_made.once
But I'd like to enforce this directly through a strict mode where the test fails if any declared stub has not been called?
I don't think this is really possible unless you do some heavy monkeypatching - and it seems like a bad idea instead of just refactoring your tests.
Here's the call I would like to test expectations for:
UserMailer.invoice_paid(user, invoice).deliver_later
Rails 5 ActionMailer behaviour in :test mode seems to return nil on every method of an ApplicationMailer class. This should be fine since I can just stub it like so:
invoice_paid_dbl = double(ActionMailer::MessageDelivery)
allow(UserMailer).to receive(:invoice_paid).and_return(invoice_paid_dbl)
This expectation fails:
expect(UserMailer).to receive(:invoice_paid).once
While this one passes:
expect(invoice_paid_dbl).to receive(:deliver_later).once
Shouldn't that be impossible? I thought maybe the method chain was confusing RSpec, but splitting up the line into this has no effect:
mail = UserMailer.invoice_paid(user, invoice)
mail.deliver_later
Adding .with(any_args) to the stub and expectation also has no effect (since that's the default anyways). The failure is expected: 1 time with any arguments, received: 0 times with any arguments.
Not getting how you have stubbed methods, but think that following will work for you
You are trying to stub chain of methods so need to use stub_chain instead of stub
UserMailer.stub_chain(:invoice_paid, :deliver_later).and_return(:invoice_paid_dbl)
I hope this will work and solve you problem. For more details please check docs
I'll leave the question up in case it helps others, but this was actually the result of a silly mistake. I had expect(UserMailer).to receive(:invoice_paid) earlier in the it block, and it was passing. It was the second expectation of that type which was failing. Oops.
I'm try to stub the Slack gem with the new syntax (old syntax throws the same error):
before :each do
allow(Slack).to receive(:channels_join).and_return(true)
end
This line throws wrong number of arguments (2 for 1). Breaking up the line into pieces, it seems like the call to .to is throwing the error:
a = allow(Slack)
b = receive(:channels_join)
c = b.and_return(true)
a.to(c)
Changing the arguments to .to didn't change anything:
a.to(c, c)
throws the same 2 for 1 error.
a.to(5)
throws a reasonable error: only the receive or receive_messages matchers are supported with allow(...).to, but you have provided: 5
Why is the 2 for 1 error being thrown?
If you have enabled the verify_partial_doubles option than RSpec will check if Slack responds to :channels_join when you stub it. Unfortunately, Slack.respond_to? is implemented incorrectly:
def self.respond_to?(method)
return client.respond_to?(method) || super
end
The problem is that Object#respond_to? accepts two arguments (and has for years, since at least 1.8.7, if not sooner!), and RSpec passes a 2nd arg, expecting that respond_to? will accept two arguments since it is supposed to.
To fix it, you can (temporarily) monkey patch the slack gem:
module Slack
def self.respond_to?(method, include_all=false)
return client.respond_to?(method, include_all) || super
end
end
This should really be fixed in the slack gem, though, so I'd encourage you to open a PR with the maintainers with this fix.
More broadly, when you run into this kind of error, you can learn more about the problem by passing rspec the -b (or --backtrace) flag, which will print the full backtrace. In your situation I would have expected it to show both the respond_to? call site within RSpec and the line where Slack defines respond_to? accepting only one argument. Then you could look at those lines to figure out what's going on.
I'm just getting started with feature specs using RSpec (and Capybara). I'm testing my ActiveAdmin dashboard and I want to check that all panels have an orders table as shown in this snippet:
feature 'admin dashboard', type: :feature do
def panels
page.all('.column .panel')
end
describe 'all panels' do
it 'have an orders table' do
expect(panels).to all(have_css('table.orders tbody'))
end
end
end
I've used the all matcher a lot in my unit tests but it doesn't appear to work when wrapping Capybara's have_css matcher because I'm getting the following error:
Failure/Error: expect(panels).to all(have_css('table.orders tbody'))
TypeError:
no implicit conversion of Capybara::RackTest::CSSHandlers into String
Am I correct in my assumption that RSpec's built-in all matcher should work with other matchers as well?
Note: I'm using describe and it instead of feature and scenario in this instance because I'm testing output rather than user interaction scenarios (see my other question).
Unfortunately there is a conflict between RSpec's all and Capybara's all see Capybara Issue 1396. The all that you are calling is actually Capybara's all.
Solution 1 - Call BuiltIn::All Directly
The quickest solution would be to call RSpec's all method directly (or at least that code that it executes.
The expectation will work if you use RSpec::Matchers::BuiltIn::All.new instead of all:
expect(panels).to RSpec::Matchers::BuiltIn::All.new(have_css('table.orders tbody'))
Solution 2 - Redefine all
Calling the BuiltIn:All directly does not read nicely so might get annoying if used often. An alternative would be to re-define the all method to be RSpec's all method. To do this, add the module and configuration:
module FixAll
def all(expected)
RSpec::Matchers::BuiltIn::All.new(expected)
end
end
RSpec.configure do |c|
c.include FixAll
end
With the change, the all in the following line will behave like RSpec's all method.
expect(panels).to all(have_css('table.orders tbody'))
Note that if you want to use Capybara's all method, you would now always need to call it using the session (ie page):
# This will work because "page.all" is used
expect(page.all('table').length).to eq(2)
# This will throw an exception since "all" is used
expect(all('table').length).to eq(2)
I used a very similar approach to the accepted answer, but in a Cucumber environment I was getting errors about RSpec.configure not existing. Also, I wanted to call the matcher something besides all so that I could use them both without conflicts. This is what I ended up with
# features/support/rspec_each.rb
module RSpecEach
def each(expected)
RSpec::Matchers::BuiltIn::All.new(expected)
end
end
World(RSpecEach) # extends the Cucumber World environment
Now I can do things like:
expect(page.all('#employees_by_dept td.counts')).to each(have_text('1'))
I have a static method that is doing HTTP POST to a remote server.
I want to test that this method is called with the correct arguments whenever its needed, but I also want to prevent it from actually running so it won't execute the actual HTTP request while testing.
how can I stub that method globally so I won't have to do it in every test case for preventing it from actually running ?
and if doing so, how do I unstub it for the specific test case of testing the actual method ?
If you simply want to stub the method in all your specs and check that it receives the correct arguments whenever it is called (assuming they are the same in each case), you can just add a stub to your spec_helper in a before(:each) block:
RSpec.configure do |config|
...
config.before(:each) do
SomeClass.stub(:some_static_method).with(...).and_return(...)
end
...
end
That will fail if the stub is called with anything but the arguments you specified. Note that you can also use should_receive as #appneadiving suggested, but you'll have to call it with any_number_of_times to make sure it doesn't fail if you don't call it, and that's basically the same as using a stub (see this discussion).
Another route is to use the webmock gem to stub requests to a given URL. Using this approach you don't (necessarily) need to stub your method, you can just let it run as usual with the certainty that whenever it tries to access the server with the arguments you specify, it will get a given response.
To use the webmock throughout your specs you'll have to add a line in spec_helper like the one above, calling the stub_request method, e.g.:
stub_request(:any, "www.example.com").with(:query => { :a => "b"}).to_return(...)
Hope that helps.