Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stubbing API Request #232

Closed
ghost opened this issue Jan 10, 2013 · 4 comments
Closed

Stubbing API Request #232

ghost opened this issue Jan 10, 2013 · 4 comments

Comments

@ghost
Copy link

ghost commented Jan 10, 2013

Hey Rick,

Instead of having to create and pass in stubs, it would be nice if this was done automatically if none are given. For one, since autoload isn't being used, you have to require the Test adapter to do so. And in either case, having access to the stubs through the Connection would be nice too.

i.e. Instead of

require 'faraday/adapter/test'
@stubs = Faraday::Adapter::Test::Stubs.new
@conn = Faraday.new do |builder|
  builder.adapter :test, @stubs
end
@stubs.get('/foo') { [200, {}, ''] }
@resp = @conn.get('/foo')

You could just use

@conn = Faraday.new do |builder|
  builder.adapter :test
end
@conn.stubs.get('/foo') { [200, {}, ''] }
@resp = @conn.get('/foo')

The following changes probably better illustrate my point:
https://github.com/burns/faraday/compare/stubbing

I'm not sure this is the correct answer. But since RackBuilder::Handler doesn't instantiate the adapter until the request, I don't see any other option. In fact, the Test adapter creates an instance of Stubs if none are passed in, but I see no way this could be used.

I believe you mentioned you were planning on some changes to RackBuilder and/or the stubbing API, so you may have other ideas. But, thought I'd throw this out there.

@technoweenie
Copy link
Member

This would be easier on the new CallbackBuilder. Instead of a single array of every middleware, it stores them in 2 arrays around the adapter. You'll be able to set the adapter much easier now without having to introspect the stack or anything like that.

What about something like:

@conn.stub do |stubs|
  stubs.get...
end

Though, I guess if you're writing proper tests, you'll only need to stub one thing :)

def setup
  @conn.stubs.clear
end

def test_foo
  @conn.stubs.get('/foo') { [200, {}, ''] }
  assert_equal 200, @conn.get('/foo').status
end

This could go in a faraday/test file that you have to require to get special testing perks.

@ghost
Copy link
Author

ghost commented Jan 10, 2013

This could go in a faraday/test file that you have to require to get special testing perks.

That's how I'm handling it now, along with a helper to pick up the connection defaults for the stubs.

require 'faraday'
class Client
  extend Forwardable
  def_delegator :@connection, :get

  def initialize(api_url, api_key)
    @connection = Faraday.new(
      url: api_url,
      params: { api_key: api_key },
      headers: { accept: 'application/json' }
    ) do |conn|
      # request/response middleware... retry, json parsing, etc.
      if respond_to?(:stubs)
        conn.adapter :test, stubs
      else
        conn.adapter Faraday.default_adapter
      end
    end
  end
end

require 'faraday/adapter/test'
module ClientHelpers
  def stubs
    @stubs ||= Faraday::Adapter::Test::Stubs.new
  end

  # Add api_url base path and default request params/headers to stub
  def stub(path, params = {}, headers = {}, &block)
    path = File.join(@connection.url_prefix.path, path) + '?' +
        Faraday::Utils.build_nested_query(params.merge(@connection.params))
    headers = @connection.headers.merge(headers)
    resp = Response.new(200, {}, '')
    block.call(resp)
    stubs.get(path, headers) { [resp.status, resp.headers, resp.body] }
  end

  class Response < Struct.new(:status, :headers, :body)
    def headers
      if body.start_with?('{')
        self[:headers].merge('Content-Type' => 'application/json;charset=utf-8')
      else
        self[:headers].merge('Content-Type' => 'text/html;charset=utf-8')
      end
    end
  end
end

Client.send(:include, ClientHelpers)

require 'minitest/autorun'
class ClientTest < MiniTest::Unit::TestCase
  def setup
    @client = Client.new('http://domain.com/api_root/', 'my_key')
  end

  def test_client
    @client.stub(
      'search', { query: 'ping' }, { 'User-Agent' => 'minitest' }
    ) {|res| res.body = 'pong' }

    res = @client.get do |req|
      req.url 'search'
      req.params['query'] = 'ping'
      req.headers['User-Agent'] = 'minitest'
    end

    assert_equal 200, res.status
    assert_equal({ 'Content-Type' => 'text/html;charset=utf-8' }, res.headers)
    assert_equal 'pong', res.body

    assert_equal(
      { 'Accept' => 'application/json', 'User-Agent' => 'minitest' },
      res.env.request_headers
    )
    assert_equal(
      { 'api_key' => 'my_key', 'query' => 'ping' },
      res.env.params
    )
    assert_equal 'http://domain.com/api_root/search?api_key=my_key&query=ping',
                  res.env.url.to_s
  end
end

Well, maybe not exactly like this, but you get the idea :)

Feel free to close this if you like, as this really isn't an issue.
Perhaps a bullet point for #202
I haven't looked at the callback-builder branch, so I may do that at some point.

@mislav
Copy link
Contributor

mislav commented Feb 12, 2013

Not a fan of @conn.stubs, i.e. a method on Connection that only makes sense for one adapter.

Here is a clean way to replace a real adapter with test one and make request stubs in the same go:

# for Faraday 0.8.x
def stub_request(conn, adapter_class = Faraday::Adapter::Test, &stubs_block)
  adapter_handler = conn.builder.handlers.find {|h| h.klass < Faraday::Adapter }
  conn.builder.swap(adapter_handler, adapter_class, &stubs_block)
end

# usage in tests:
stub_request(conn) do |stub|
  stub.get '/nigiri/sake.json' do
    [200, {}, 'hi world']
   end
end

@iMacTia
Copy link
Member

iMacTia commented Feb 9, 2017

Closing as alternative solution provided above

@iMacTia iMacTia closed this as completed Feb 9, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants