Ruby stubbing with faraday, can't get it to work
Asked Answered
B

5

12

Sorry for the title, I'm too frustrated to come up with anything better right now.

I have a class, Judge, which has a method #stats. This stats method is supposed to send a GET request to an api and get some data as response. I'm trying to test this and stub the stats method so that I don't perform an actual request. This is what my test looks like:

describe Judge do
  describe '.stats' do

    context 'when success' do
      subject { Judge.stats }

      it 'returns stats' do
        allow(Faraday).to receive(:get).and_return('some data')

        expect(subject.status).to eq 200
        expect(subject).to be_success
      end
    end
  end
end

This is the class I'm testing:

class Judge

  def self.stats
    Faraday.get "some-domain-dot-com/stats"
  end

end

This currently gives me the error: Faraday does not implement: get So How do you stub this with faraday? I have seen methods like:

    stubs = Faraday::Adapter::Test::Stubs.new do |stub|
      stub.get('http://stats-api.com') { [200, {}, 'Lorem ipsum'] }
    end

But I can't seem to apply it the right way. What am I missing here?

Bustos answered 26/2, 2014 at 18:42 Comment(1)
Faraday doesn't receive :get, the instance of Faraday::Connection it returns does. You would need to stub Faraday to return an instance double of Faraday::Connection when it receives :newTifanie
P
12

Note that Faraday.new returns an instance of Faraday::Connection, not Faraday. So you can try using

allow_any_instance_of(Faraday::Connection).to receive(:get).and_return("some data")

Note that I don't know if returning "some data" as shown in your question is correct, because Faraday::Connection.get should return a response object, which would include the body and status code instead of a string. You might try something like this:

allow_any_instance_of(Faraday::Connection).to receive(:get).and_return(
   double("response", status: 200, body: "some data")
)

Here's a rails console that shows the class you get back from Faraday.new

$ rails c
Loading development environment (Rails 4.1.5)
2.1.2 :001 > fara = Faraday.new
 => #<Faraday::Connection:0x0000010abcdd28 @parallel_manager=nil, @headers={"User-Agent"=>"Faraday v0.9.1"}, @params={}, @options=#<Faraday::RequestOptions (empty)>, @ssl=#<Faraday::SSLOptions (empty)>, @default_parallel_manager=nil, @builder=#<Faraday::RackBuilder:0x0000010abcd990 @handlers=[Faraday::Request::UrlEncoded, Faraday::Adapter::NetHttp]>, @url_prefix=#<URI::HTTP:0x0000010abcd378 URL:http:/>, @proxy=nil>
2.1.2 :002 > fara.class
 => Faraday::Connection
Plenty answered 25/3, 2016 at 21:2 Comment(1)
This works but it's quite dangerous to stub with allow_any_instance_of; this will result in a passing test if the Faraday client (Faraday::Connection instance) makes any GET request -- not necessarily the one you're testing for. It's safer to stub Faraday.new to return an instance double of Faraday::Connection and to also only use receive in conjunction with with to validate the correctness of the arguments you're using.Tifanie
C
6

Coming to this late, but incase anyone else is too, this is what worked for me - a combination of the approaches above:

  let(:json_data) { File.read Rails.root.join("..", "fixtures", "ror", "501100000267.json") }

  before do
    allow_any_instance_of(Faraday::Connection).to receive(:get).and_return(
      double(Faraday::Response, status: 200, body: json_data, success?: true)
    )
  end
Clova answered 24/3, 2021 at 15:12 Comment(1)
This was helpful for me. Thank youNeves
M
2

Faraday the class has no get method, only the instance does. Since you are using this in a class method what you can do is something like this:

class Judge
  def self.stats
    connection.get "some-domain-dot-com/stats"
  end

  def self.connection=(val)
    @connection = val
  end

  def self.connection
    @connection ||= Faraday.new(some stuff to build up connection)
  end
end

Then in your test you can just set up a double:

let(:connection) { double :connection, get: nil }
before do
  allow(connection).to receive(:get).with("some-domain-dot-com/stats").and_return('some data')
  Judge.connection = connection
end
Mobcap answered 26/2, 2014 at 19:8 Comment(7)
This looks neat. Getting "Double :connection received unexpected message :stub with (:get)", tho. Which one is unexpected the :stub method or the :get? :sBustos
What version of rspec are you using? Are you using rspec mocks or some other mocking library?Mobcap
rspec: 3.0.0.beta2, WebMock: 1.17.3Bustos
Hmm, what I put there is valid rspec for both 2.x and 3.0. WebMock is primarily for mocking web requests so shouldn't really come into play. It's complaining that the connection double didn't like the stub method, but that doesn't make much sense.Mobcap
I agree, can't figure it out either.Bustos
"Faraday the class has no get method" <-- That's actually incorrect. Faraday.get is a shortcut which uses the default stack. That stack doesn't include any stubs, though, which is why the request isn't stubbed. github.com/lostisland/faraday#usageMyself
(Incidentally, I have no idea where "Faraday does not implement: get" is coming from. It's not a NoMethodError.)Myself
E
1

I ran into the same problem with Faraday::Adapter::Test::Stubs erroring with Faraday does not implement: get. It seems you need to set stubs to a Faraday adapter, like so:

  stubs = Faraday::Adapter::Test::Stubs.new do |stub|
    stub.get("some-domain-dot-com/stats") { |env| [200, {}, 'egg'] }
  end

  test = Faraday.new do |builder|
    builder.adapter :test, stubs
  end

  allow(Faraday).to receive(:new).and_return(test)

  expect(Judge.stats.body).to eq "egg"
  expect(Judge.stats.status).to eq 200
Ephraim answered 18/3, 2016 at 21:48 Comment(0)
P
1

A better way to do this, rather than using allow_any_instance_of, is to set the default connection for Faraday, so that Faraday.get will use the connection you setup in your tests.

For example:

  let(:stubs) { Faraday::Adapter::Test::Stubs.new }
  let(:conn) { Faraday.new { |b| b.adapter(:test, stubs) } }

  before do
    stubs.get('/maps/api/place/details/json') do |_env|
      [
        200,
        { 'Content-Type': 'application/json' },
        { 'result' => { 'photos' => [] } }.to_json
      ]
    end

    Faraday.default_connection = conn
  end

  after do
    Faraday.default_connection = nil
  end
Pyles answered 15/11, 2021 at 14:31 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.