POST params to a stubbed rack app with rspec and webmock - ruby-on-rails

Using Rails 4.2.3, Rspec 3
I want to pass post params to a fake Rack app to respond accordingly in my tests.
My Fake app:
# spec/support/fake_mangopay.rb
class FakeMangopay < Sinatra::Base
attr_accessor :user
post '/:version/oauth/token' do
json_response :post, 200, 'token.json'
end
...
private
def json_response(method, response_code, file_name)
content_type :json
status response_code
File.open("#{File.dirname(__FILE__)}/fixtures/mangopay/#{method}/#{file_name}", 'rb').read
end
end
How I stub the requests:
# spec/spec_helper
config.before(:each) do
stub_request(:any, /api.sandbox.mangopay.com/).to_rack(FakeMangopay)
...
end
I currently have simple (and static) JSON files, and I would like to make them json.erb files.
But I don't know how to get those post parameters..
Help?

Found out that I can access request.body to get what I want.
params = JSON.parse request.body.read

Related

NoMethodError: undefined method `submission' for HTTParty

I'm currently working on a Rails application where I am trying to submit a form to the FormStack API. The request look as follows.
This is what the requests looks like:
POST /api/v2/form/12345/submission.json HTTP/1.1
Host: www.formstack.com
Authorization: Bearer YOUR_APP_OAUTH_TOKEN
Accept: application/json
Content-Type: application/json
field_12345=Example&field_12346=Answer
I'm trying to implement that using Httparty on the library I created to make the requests to this API service.
module FormStack
class Form
include HTTParty
attr_reader :form_id
base_uri "https://www.formstack.com/api/v2"
def initialize
#access_token = ENV.fetch('FORMSTACK_ACCESS_TOKEN')
#form_id = ENV.fetch('FORMSTACK_FORM_ID')
end
def create_form
self.class.get(relative_uri, headers: headers)
end
def submission
self.class.post(create_submission_uri, headers: headers, query: query)
end
private
def relative_uri
"/form/#{#form_id}/field.json"
end
def create_submission_uri
"form/#{#form_id}/submission.json"
end
def headers
{
"Accept" => "application/json",
"Content-Type" => "application/json",
"Authorization" => "Bearer #{#access_token}"
}
end
def query
{
"field_66563890" => "blah",
"field_66563757" => "something"
}
end
end
end
controller
class FormsController < ApplicationController
def display_form
#form = FormStack::Form.new().create_form
end
def create
#form.submission
redirect_to 'localhost:3000'
end
end
This are the routes
get '/forms/display_form', to: 'forms#display_form'
post '/forms/submit', to: "forms#create"
First of all, I've got a couple general ruby things for you:
When you call FormStack::Form.new().create_form you actually don't need the () after .new -- ruby knows to call the method with no arguments even if you exclude the parens.
I'm not quite sure how you're calling FormsController::display_form from FormsController::create, but for now I'll just assume that you're using magic.
Anyways, on to my answer. As your error message states, the error is related to you calling submission on something which does not have a submission method. With that knowledge, we can look at what Object you're calling submission on in this line:
#form.submission
It looks like you're calling submission on #form. Well, let's go and look at where you declare #form:
#form = FormStack::Form.new().create_form
Let's break that declaration down into its parts. First, with FormStack::Form.new(), you're creating a new instance of FormStack::Form. So far so good. FormStack::Form has a submission method defined on it. But then, you call create_form on it. So, let's look at what create_form does:
def create_form
self.class.get(relative_uri, headers: headers)
end
create_form calls a method provided by HTTParty, get. The get method returns a HTTParty::Response Object. So, let's parse through the line where you set #form again. Broken down, what you're doing is this:
#form = FormStack::Form # This line sets the variable to a constant
#form = #form.new # This line sets the variable to be an instance of FormStack::Form
#form = #form.create_form # This line sets #form to be an instance of HTTParty::Reponse
As you can see, at the end we've set #form to an instance of HTTParty::Reponse instead of FormStack::Form, and since there's not submission method for HTTParty::Response that's why you get the error.
Based on this exploration, we can see that the fix would be to set #form to a FormStack::Form object instead, which we can do by changing the display_form action to be:
def display_form
#form = FormStack::Form.new
#form.create_form
end

Overriding the default Content-Type

I have a Rails API which accepts only JSON as input. If I fail to include a header of Content-Type: application/json, then request.headers['Content-Type'] defaults to application/x-www-form-urlencoded and the params do not get parsed properly. The whole json body becomes a key in the params. The result is a 422, which is confusing to API users.
How can I change this to default to parsing as json if no Content-Type header is supplied?
Lots of other questions answer how to do this with the response format. To change this default, you can specify it in the controller with:
request.format = :json
Or in a route namespace with something like:
namespace :api, defaults: {format: :json} do
This, however, changes the default response format and does not change the default request format. What I need to do is to change the default request format for parsing parameters.
Here is my admittedly terrible solution derived from the suggestion in Micael Nussbaumer's answer. I'd love it if some Rubyists could magically turn this ugly hack into a pithy one liner.
module Api
class BaseApiController < ActionController::API
private
# This is an ugly hack needed to make it default to json if you do not
# specify a Content-Type. If you see this and know of a better way please
# say so!
def params
if !#params
if request.headers["Content-Type"]=="application/x-www-form-urlencoded"
body_string = request.body.read
begin
hash = JSON.parse(body_string)
#params = ActionController::Parameters.new(hash)
rescue
# do nothing
end
end
if !#params
#params = super
end
end
#params
end
...
end
I've solved it with middleware this way for Rails API (rails new my_project --api)
config:
# config/application.rb
# ...
require './lib/middleware/consider_all_request_json_middleware'
# ...
module MyApplication
# ...
class Application < Rails::Application
# ...
config.middleware.insert_before(ActionDispatch::Static,ConsiderAllRequestJsonMiddleware)
# ...
middleware:
# lib/middleware/consider_all_request_json_middleware.rb
class ConsiderAllRequestJsonMiddleware
def initialize app
#app = app
end
def call(env)
if env["CONTENT_TYPE"] == 'application/x-www-form-urlencoded'
env["CONTENT_TYPE"] = 'application/json'
end
#app.call(env)
end
end
original: https://blog.eq8.eu/til/content-type-applicationjson-by-default-in-rails-5.html
parsed = JSON.parse(json_body) unless request.headers["Content-Type"] == 'application/json'

Saving rails request to JSON/YML

I'm using service which sends Webhooks to my application. I want to write RSpec test for handling them. It's important to have this request exactly the same (remote caller IP, headers with encrypted content).
I tried to save request as json:
class WebhookController < ApplicationController
def some_callback
File.open('temp/request_example.json','w') do |f|
f.write request.to_json
end
end
end
so I could later do:
describe WebhookController do
subject { get :some_callback, JSON.parse(File.open('temp/request_example.json')) }
it 'does something' do;end
end
but unfortunately you cannot call request.to_json(request.to_json
IOError: not opened for reading). You can't either get directly to request.body or request.headers.
How to save such request for later usage in tests? Is there any gem for it?

Setting Content-Type header for RSpec and Rails-API

I'm using the rails-api gem to build a web service and want to test my API with RSpec. Every request I make, regardless of the HTTP method has the CONTENT_TYPE header set as "application/x-www-form-urlencoded". This isn't really a problem until I try to use wrap_parameters in my controller and it's not have any affect on the params hash:
class ApplicationController < ActionController::API
include ActionController::ParamsWrapper
end
class ProjectsController < ApplicationController
wrap_parameters :project, include: [:name]
# ...
end
This hack no longer works (#request is nil), and none of the other Stack Overflow posts I found work either.
If I make the following request in my RSpec test:
put "/projects/1.json", {name: 'Updated Project 1'}
and put a debugger in my controller I get:
(rdb:1) p params
{ "name"=>"Updated Project 1",
"action"=>"update",
"controller"=>"projects",
"id"=>"5539bbd9-010c-4cfb-88d3-82dadbc99507",
"format"=>"json"
}
(rdb:1) p request.content_type
"application/x-www-form-urlencoded"
I'm expecting to see something like this for the params hash (note the addition of the project key):
{ "name"=>"Updated Project 1",
"action"=>"update",
"controller"=>"projects",
"id"=>"5539bbd9-010c-4cfb-88d3-82dadbc99507",
"format"=>"json",
"project" => {"name" => "Updated Project 1"}
}
Is it possible to set the content type header using just RSpec? Or do I have have to use rack/test for this functionality?
A lot of frustration and variations and that's what worked for me.
Rails 3.2.12 Rspec 2.10
#request.env["HTTP_ACCEPT"] = "application/json"
#request.env["CONTENT_TYPE"] = "application/json"
put :update, :id => 1, "email" => "bing#test.com"
wrap_parameters seems to be working declared this way
wrap_parameters User, format: :json
being used for User model
This worked for me Rails 4.0.3 and Rspec 2.14.1 if anyone is looking for more recent versions.
put '/projects/1.json', {name: 'Updated Project 1'}, {
'HTTP_ACCEPT' => 'application/json',
'CONTENT_TYPE' => 'application/json'
}
and
wrap_parameters Project, format: :json
Using the new Rails v5.0.x API only settings I found that this problem with rails defaulting everything to "application/x-www-form-urlencoded" is still in issue for testing with RSpec-Rails Requests
Here is what I did to fix the problem:
Create support file at ./spec/support/json_requests.rb
Edit it to be something like this to override the behavior for all of your API only JSON requests:
module JsonRequests
def get(*args)
super(*json_args(*args))
end
def post(*args)
super(*json_args(*args))
end
def update(*args)
super(*json_args(*args))
end
def patch(*args)
super(*json_args(*args))
end
def put(*args)
super(*json_args(*args))
end
def delete(*args)
super(*json_args(*args))
end
def json_args(path, params = {}, headers = {})
[path, params.to_json, headers.merge('CONTENT_TYPE' => 'application/json')]
end
end
RSpec.configure do |config|
config.include JsonRequests, type: :request
end
Keep in mind that this will override all Specs within ./spec/requests so if you need to use "application/x-www-form-urlencoded" you could also include this module manually as needed in your Describe 'something' do block.
Rails 5 no hacks:
put(:update,
params: {project_id: 1},
body: {name: 'Updated Project 1'}.to_json,
as: :json)
This sets the content_type correctly. In the controller params will hold both params and body.
Its 2021, Rails 6.1 and I had to use as: :json to fix this wierd mangling of an array of hashes in the params.
put(:update, params: the_params_hash, as: :json)
If you are using Rails 4 (and rspec ~3.7) and don't want to use the inline syntax:
request.headers["CONTENT_TYPE"] = "application/json"
Rails 5
headers = { 'CONTENT_TYPE' => 'application/json' }
params = { user_type: 'tester' }
and after that request like
post '/api/v1/users/test', params.to_json, headers
and also remove .to_json from request route

Mocha not mocking a class method in a functional test (Rails 3)

In a rails 3 app, I'm using mocha to do some mocking in my functional tests. However it doesn't seem to mock a class method in the functional controller.
Controller code
class TagsController < ApplicationController
respond_to :json
def index
response = User.tags_starting_with(params[:query])
respond_with response
end
end
Functional test
class TagsControllerTest < ActionController::TestCase
context "index action with query" do
setup do
query = "A_QUERY"
get :index, :query => query, :format => "json"
#tags = ["these", "are", "test", "tags"]
User.expects(:tags_starting_with).returns(#tags).once
end
should "return JSON formatted tags array" do
tags = JSON::parse #response.body
assert_equal #tags, tags
end
end
end
Gemfile
gem "mocha"
If I run this test, I keep running into
- expected exactly once, not yet invoked: User.tags_starting_with(any_parameters)
If I use rails console test I can mock a class method just fine, and it works as expected.
I've been through this post and have done the Gemfile, require "false" bit. But to no avail, it just doesn't want to mock the class method of the User in the controller.
Other things I've tried, if I do User.tags_starting_with("bla") in the test itself, the expectation passes.
So any ideas on why the User in the controller isn't being mocked correctly?
As said on Twitter:
You're setting you your mock after you're doing your request :-)

Resources