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?

Majoren
  • 983
  • 5
  • 16
  • 36
  • 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 :new – Allison Aug 20 '21 at 17:37

5 Answers5

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
TheGeorgeous
  • 3,927
  • 2
  • 20
  • 33
Daniel
  • 1,789
  • 17
  • 15
  • 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. – Allison Aug 20 '21 at 17:42
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
Paul Danelli
  • 994
  • 1
  • 15
  • 25
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
Nuno Costa
  • 1,210
  • 11
  • 12
Alex Peachey
  • 4,606
  • 22
  • 18
  • This looks neat. Getting "Double :connection received unexpected message :stub with (:get)", tho. Which one is unexpected the :stub method or the :get? :s – Majoren Feb 26 '14 at 19:35
  • What version of rspec are you using? Are you using rspec mocks or some other mocking library? – Alex Peachey Feb 26 '14 at 19:37
  • rspec: 3.0.0.beta2, WebMock: 1.17.3 – Majoren Feb 26 '14 at 19:40
  • 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. – Alex Peachey Feb 26 '14 at 19:45
  • I agree, can't figure it out either. – Majoren Feb 26 '14 at 19:53
  • 2
    "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. https://github.com/lostisland/faraday#usage – Peeja Mar 26 '14 at 20:33
  • 1
    (Incidentally, I have no idea where `"Faraday does not implement: get"` is coming from. It's not a `NoMethodError`.) – Peeja Mar 26 '14 at 20:34
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
Pete
  • 10,310
  • 7
  • 53
  • 59
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
Zachary Wright
  • 23,480
  • 10
  • 42
  • 56