Stubbing API calls in controller with rspec - ruby

I am just a little confused at why I can't stub a local variable in my controller spec.
Here is my controller:
Class UsersController < ApplicationController
...
def get_company
resp = Net::HTTP.get("http://get_company_from_user_id.com/#{params[:id]}.json")
#resp = JSON.parse(resp.body)
...
My spec looks like:
class ResponseHelper
def initialize(body)
#body = body
end
end
describe "Get company" do
it "returns successful response" do
stub_resp_body = '{"company": "example"}'
stub_resp = ResponseHelper.new(stub_resp_body)
controller.stub!(:resp).and_return(stub_resp)
get :get_company, {:id => #test_user.id}
expect(response.status).to eq(200)
end
end
I still get an error saying that:
Errno::ECONNREFUSED:
Connection refused - connect(2)
What am I doing wrong? If I am stubbing the resp variable why is it still trying to do the HTTP request and how would I stub the resp variable in this case?

You just cannot stub a local variable, you can only stub methods. In your case you can stub the Net::HTTP.get method:
Net::HTTP.stub(:get).and_return(stub_resp)

There is no such thing as 'stubbing a local variable'. The only thing that can be stubbed are method calls.
You would need the stub the Net::HTTP.get call to return something that looks like a Net::HTTPResponse that the rest of your code can work with.
I quite often like to tidy this up by having a client class for each api that knows how to generate the url from the arguments (in this case the id) and how to parse the response. This keeps those details out of the controller and also makes testing easy, since now you can provide a mock client object

You cannot stub a local variable. Just a method. As there were answers above you may want to stub Net::HTTP.get call. However, if you don't want to have you code rely upon a particular HTTP client library you can extract the http request into another method of a controller and stub this method
Class UsersController < ApplicationController
...
def get_company
resp = make_request(params[:id)
#resp = JSON.parse(resp.body)
end
protected
def make_request(id)
Net::HTTP.get('http://get_company_from_user_id.com/#{id}.json')
end
controller.
should_receive(:make_request).
with(#test_user.id).
and_return(stub_resp)

Related

Making the body of a function use a mock of an object

I have the following method in a controller:
def webhook
data_json = JSON.parse(request.body.read) # it comes from the test, it's OK
event = Stripe::Event.retrieve(data_json[:id]) # it's not OK, it's a real request to Stripe
stripe_cust_id = event.data.object.customer
user = User.where(stripe_customer_id: stripe_cust_id)
#.....
In a spec file I create a mock for event and then make a post request to webhook in a test. I'm not allowed to change the body or signature of webhook because I'm testing it. So how do I make it use the mock I create?
describe '#webhook' do
it 'something' do
user = FactoryGirl.create(:user)
event = StripeMock.mock_webhook_event('invoice.payment_succeeded')
post(:webhook, event.to_json)
#error because webhook makes a real request to Stripe
mock(Stripe::Event).retrieve(any) do
event
end
That should return event from any call to retrieve on Stripe::Event. Works with rr.
If your concern is only about external requests, you can try something like Vcr or WebMock to mock the responce.

RSpec - unable to stub class private method

I am trying to stub out the method that makes an external request for some JSON using RSpec 3. I had it working before by placing this into the spec_helper.rb file, but now that I refactored and moved the method into it own class, the stub no longer works.
RSpec.configure do |config|
config.before do
allow(Module::Klass).to receive(:request_url) do
JSON.parse(File.read(File.expand_path('spec/fixtures/example_data.json')))
end
end
end
the class looks like this
module Module
class Klass
# public methods calling `request_url`
...
private
def request_url(url, header = {})
request = HTTPI::Request.new
request.url = url
request.headers = header
JSON.parse(HTTPI.get(request).body)
end
end
end
Despite keeping the spec_helper.rb the same and trying to place the stub right before the actual spec, an external request is still being made.
Your request_url method is an instance and not a class method, therefore you have to write:
allow_any_instance_of(Module::Klass).to receive(:request_url) do
JSON.parse(File.read(File.expand_path('spec/fixtures/example_data.json')))
end

testing private before filter that relies on request body in rspec

I have the following controller:
class ApiController < ApplicationController
before_filter :authenticate_user_from_token!
private
def authenticate_user_from_token!
#json = JSON.parse(request.body.read)
--auth logic goes here extracting user credentials goes here--
request.body.rewind
if auth_valid
authenticate
else
render nothing: true, status: :unauthorized
end
end
end
Testing this method has proven to be surprisingly difficult. I have tried the following approaches:
1) Sending the private method directly:
#controller.send(authenticate_user_from_token!)
The flaw here, is that I am unsure how to mock out request.body to contain valid/invalid credentials. I have tried the following:
before do
class Hash
def body
self['body']
end
end
#request = {}
#request['body'] =StringIO.new({auth_goes_here}.to_json)
end
However, in the method, request still gets overriden with a brand new ActionController::TestRequest.
2) Posting directly:
before do
post :authenticate_user_from_token!, my_preconstructed_credentials, format: :json
end
which results in:
*** AbstractController::ActionNotFound Exception: The action 'authenticate_user_from_token!' could not be found for ApiController
3) Defining an exposed method at runtime:
before do
#controller.class_eval <<-RUBY_EVAL
public
def update
end
RUBY_EVAL
end
followed by post :update, which still results in *** NoMethodError Exception: undefined methodfoobar' for #`
My question is: how can I test a private before filter on a controller that depends on request.body? I realize I could mock out request and body in my third approach, but I would still require it to respond to read/rewind. How is this kind of method usually tested?
In the end, the following worked:
before do
class ::TestApiController < ApiController
def hello
render nothing: true
end
end
Rails.application.routes.draw do
match 'hello', to: 'test_api#hello'
end
#controller = TestApiController.new
#request.env['RAW_POST_DATA'] = my_awesome_json
post :hello
end

Ruby API wrapper module

I'm writing a wrapper for a third party REST API using HTTParty. I would like to be able to call the third party API using a call to my local module like this:
APIWrapper::APIObject::APIMethod
I would like to write a magic function inside APIWrapper that accepts the function call and decodes the Object and Method to make a call to the third party API. So the call above would do something like this:
params = {
'format' = 'json',
'object' = api_object,
'method' = api_method,
}
get(APIWrapper::BASE_URI, {:query => params})
I want to be able to populate api_object and api_method automatically based on the method called so I don't have to explicitly write a method for every API call I want to be able to make. In PHP, this would be done using a magic __get method. Is there an equivalent in Ruby?
This can definitely be done in Ruby. You need to implement const_missing on APIWrapper, which will return an object that implements method_missing to get the API method part:
module APIWrapper
def const_missing(const_name)
anon_class = Class.new do
def method_missing(name, *params, &block)
params = {
'format' = 'json',
'object' = const_name.to_s,
'method' = name.to_s,
}
get(APIWrapper::BASE_URI, {:query => params})
end
end
end
end

How would I write test for following controller

I am new to rspec and was wondering how could I write functional test for following two action of controller
class FeedbackFormsController < ApplicationController
before_filter :authenticate_user!
def new
#feedback_form = FeedbackForm.new
session[:return_to] = request.referer
end
def create
feedback_form = FeedbackForm.new(params[:feedback_form])
FeedbackMailer.new(feedback_form).deliver
flash[:notice] = "Your feedback was submitted successfully."
redirect_to session[:return_to]
end
end
I think it's probably a better learning experience for you to do it yourself, but I'll get you started with some pseudo-ish code. I will be purposely lax with my syntax.
require spec-helper
describe FeedbackFormsController
before each
controller should receive :authenticate_user! and return true
describe new
it should assign a new feedback form
get new
assigns feedback_form should be a new Feedbackform
it should call for the referer
request should receive referer
get new
it should set session value
request stub referer and return nonsense
expect
get new
to change session return_to to nonsense
describe create
it should create a new Feebackform
Feebackform should receive new with nonsense
post create nonsense
it should create a new Feebackmailer
mock = mock_model Feedbackform
Feedbackform stub new and return mock
Feedbackmailer should receive new with mock
post create nonsense
it should deliver a message
mock = mock_model FeedbackMailer
Feedbackform stub new
FeedbackMailer stub new and return mock
mock should receive deliver
post create nonsense
it should redirect
Feedbackform stub new
Feedbackmailer stub new
post create nonsense
response should redirect to session[return to]
Hopefully that should get you started.

Resources