RSpec stub throws wrong number of arguments error - ruby-on-rails

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.

Related

How do I troubleshoot "wrong number of arguments" in solidus affirm

Struggling to get solidus_affirm working and hoping anyone might have some ideas. I always have trouble with "wrong number of arguments" errors like this.
Can get through checkout all the way to the confirm step, where it throws a 500.
ArgumentError in Spree::CheckoutController#update
wrong number of arguments (given 2, expected 1)
Extracted source (around line #28):
def post(path, **data)
connection.post(normalized_path(path), data)
end
affirm-ruby (1.1.2) lib/affirm/client.rb:28:in 'post'
affirm-ruby (1.1.2) lib/affirm/client.rb:10:in 'public_send'
affirm-ruby (1.1.2) lib/affirm/client.rb:10:in 'request'
affirm-ruby (1.1.2) lib/affirm/charge.rb:7:in 'authorize'
solidus_affirm (4f8d9ee63345) lib/solidus_affirm/affirm_client.rb:18:in 'authorize'
solidus_core (3.1.5) app/models/spree/payment_method.rb:40:in 'authorize'
if I put a binding.pry in lib/affirm/charge.rb:7:in 'authorize' of the affirm gem, and try the respond Client.request method there, I get a faraday deprecation warning, not sure if that's the cause or unrelated. Seems like it's injecting an auth header or something?
pry(Affirm::Charge)> respond Client.request(:post, "charges", checkout_token: token)
WARNING: 'Faraday::Connection#basic_auth' is deprecated; it will be removed in version 2.0.
While initializing your connection, use '#request(:basic_auth, ...)' instead.
See https://lostisland.github.io/faraday/middleware/authentication for more usage info.
In that same binding.pry, I tried a few things to see what came back:
[2] pry(Affirm::Charge)> respond Client.request(:post)
ArgumentError: wrong number of arguments (given 1, expected 2)
pry(Affirm::Charge)> respond Client.request(:post, "charges")
WARNING: 'Faraday::Connection#basic_auth' is deprecated; it will be removed in version 2.0.
While initializing your connection, use '#request(:basic_auth, ...)' instead.
See https://lostisland.github.io/faraday/middleware/authentication for more usage info.
ArgumentError: wrong number of arguments (given 2, expected 1)
Any help troubleshooting the error would be much appreciated.
Apparently the error was caused by a change in the behavior of the double splat operator in Ruby 3 and needs to be fixed in the affirm-ruby gem. I submitted a PR and overrode the methods in an initializer.
In Ruby 3.0, positional arguments and keyword arguments will be separated.
Ruby 2.7 will warn for behaviors that will change in Ruby 3.0. If you
see the following warnings, you need to update your code:
Using the last argument as keyword parameters is deprecated, or
Passing the keyword argument as the last hash parameter is deprecated,
or Splitting the last argument into positional and keyword parameters
is deprecated In most cases, you can avoid the incompatibility by
adding the double splat operator. It explicitly specifies passing
keyword arguments instead of a Hash object. Likewise, you may add
braces {} to explicitly pass a Hash object, instead of keyword
arguments.
Source: https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0/
The splat operator was breaking things: request(method, path, **data)
I created an initializer at config/initializers/affirm-ruby.rb to override the gem's methods in our Solidus app.
module Affirm
class Client
class << self
def request(method, path, data={})
new.public_send(method, path, data)
end
end
def get(path, data={})
connection.get(normalized_path(path), data)
end
def post(path, data={})
connection.post(normalized_path(path), data)
end
end
end
The problem is that the call to request() is ambiguous.
respond Client.request(:post, "charges", checkout_token: token)
Looking at the source for the request() method, it takes two specific positional arguments, followed by any number of positional arguments. That's the problem: it only takes positional arguments. Your call includes a keyword argument of checkout_token, but the request() method doesn't know what that is, because there's no checkout_token keyword parameter on the method definition.
If you need to give a method a hash, you should give it exactly that, just like the article you linked suggests:
respond Client.request(:post, "charges", { checkout_token: token })

how to stub a method for a specific argument in Rspec

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

RSpec expectation on stub fails

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.

Rails- upgrading to ruby 2.2.2 - no implicit conversion of Array into String (TypeError)

very new to ruby and rails.. Ive been working on a project, that simply reads in files and parses them to store into a database. Project was running fine, however running the code after an update to ruby 2.2.2 , I received an error that wasn't previously there:
in `foreach': no implicit conversion of Array into String (TypeError)
Here is the snippet of the foreach thats causing an error: (let me know if more code is necessary).
def parse(file_name)
File.foreach(file_name).with_index do |line, line_num|
puts "\nLine #{line_num}: #{line}"
Does anyone know whats going on?
Thank you
EDIT: Sorry it was a snippet! Im calling this ruby code into my rails Test called "parse_log_file_test"
require File.dirname(__FILE__) + '/../test_helper'
class ParseLogFileTest < ActiveSupport::TestCase
filename = Array.new
Dir.glob('database/oag-logs/*.log').each do |log_file|
filename.push(log_file)
end
parser = ParseLogFile.new
parser.parse(filename)
test 'parse' do
parser = ParseLogFile.new
filename.each do |log_file|
begin
parser.parse(log_file)
rescue
puts"ERROR: Unable to parse line #{log_file}"
end
end
assert true
end
end
I'm guessing you omitted the end to your function, but if you don't have it, you need it.
This error indicates that the argument passed to parse as file_name is an array instead of a string.
However, if that's the case, it fails the same on e.g. Ruby 1.8.4:
File.foreach([]).with_index do |line, line_num|
puts "\nLine #{line_num}: #{line}"
end
Output:
TypeError: can't convert Array into String
from (irb):1:in `foreach'
from (irb):1:in `with_index'
from (irb):1
from :0
Thus my guess is that the code that produces the value you pass to parse returned a string in your previous Ruby version and returns an array in 2.2.2.
In your case, the error is caused by the first invocation of parser.parse(...), right above the test 'parse' do line, not by the invocation inside the test method. I guess you put that invocation there after the migration, probably to debug a problem. But you are passing a different argument to the first invocation than to the invocation inside the test method, so it fails for a different reason than the one in the test method.
To see what Error is caused inside your test, simply remove the lines
rescue
puts"ERROR: Unable to parse line #{log_file}"
(Keep the end or you'll have to remove the begin, too.)
This way, an Error will hit the test runner, which will usually display it including message and stack trace.
Agreed with the poster above that you are most likely missing quotation marks. It should have nothing to do with 2.2.2 though, probably you are copy-pastying your file name differently this time around.
So apparently this is an issue with upgrading to ruby 2.2.2 on windows. After getting past this error, I only encountered more errors.. nokogiri..etc
I have recently got a mac and the errors went away.

Testing module without Rails

I've got some Rspec tests that I'm using to test a module. The module is used by a number of models in my Rails app. In order to keep testing time down to a minimum, I want to avoid loading Rails when testing my module.
The problem is that there are a few methods that contain rescues and those rescues mention the Rails debugger. When Rspec sees the capital 'r' in Rails, it has a bad time because it considers it an uninitialized constant.
I was hoping that if I wrapped up the logging info into its own method, I could avoid the problem. Here's what I came up with. But if I go down this road, I would need to stub either the rescue code or or the log_info method. Is that right? How would I do that? And which one should I stub?
def download_robots_file(page)
file = Net::HTTP.get(URI("#{page}robots.txt"))
rescue StandardError => ex
log_info('robot_file', ex)
end
....
private
def log_info(problem, exception_issue)
Rails.logger.debug("Couldn't download #{problem}: " + exception_issue.inspect)
end
You can add to stub chain method that you want to stub.
Rails.stub_chain(:logger).and_return(mocked_logger_instance)
Un-stub in the end with:
Rails.unstub(:logger)
All credits go to mister on following link How to rspec mock ruby rails logger class

Resources