RSpec: setting request headers - ruby-on-rails

I'm trying to set important headers with ActionDispatch request helper method in my specs:
RSpec.describe API::V1::FoosController, type: :request do
describe 'GET #index' do
context 'common case' do
request.env.merge!({'HTTP_FOO': 'FOO'})
get api_foos_path, {}, {Accept: Mime::JSON}
end
end
end
But this header (and generally any header set with request) disappears when it comes to controller:
class API::V1::FoosController < ApplicationController
respond_to :json, :xml
def index
request.env.has_key? 'HTTP_FOO' # false
respond_with serialize_models(Foo.all)
end
# ...
end
Why does it happen and how do I set it properly?
Setting the header with request.header or #request.header does the same.
P.S.: I know I can set headers as a third parameter of Rack::Test::Methods helpers, but I don't want to violate DRY - I'd like Mime formats only to be defined there.

Please try like this:
request.env['HTTP_FOO_HEADER'] = 'foo header'

Use controller.request.headers:
controller.request.headers['HTTP_FOO'] = 'FOO'
I can verify that this approach works in Rails 4.2.5, as this is lifted directly from real-world code.
Our tests look like this:
describe SomeController, type: :controller do
let(:current_user) { create :user }
before :each do
controller.request.headers['Authorization'] = "APIKey #{current_usser.api_key}"
end
end
And our ApplicationController looks (more or less) like this:
before_action :authenticate_request
def authenticate_request
key = request.headers['Authorization']
user = User.find_by(api_key: key)
# raise AuthenticationError unless user, etc etc
end

Related

Rails strong parameters - Request allowed without required key

I'm working on a Rails API and I'm using strong parameters in the controllers. I have a request spec that is failing for one model but works on all other models. The controllers for each model are pretty much all the same.
As you can see below in the spec, the request body SHOULD be { "tag": { "name": "a good name" }}. However, this spec is using { "name": "a good name" } which SHOULD be invalid because it's missing he "tag" key. This same spec for the same controller functionality works fine for plenty of other models.
Another interesting twist is that if I change the controller's strong parameter to params.require(:not_tag).permit(:name) it throws an error for not including the "not_tag" key.
Ruby: 2.6.5p114
Rails: 6.0.1
Expected response status: 422
Received response status: 201
Controller
class TagsController < ApplicationController
before_action :set_tag, only: [:show, :update, :destroy]
# Other methods...
# POST /tags
def create
#tag = Tag.new(tag_params)
if #tag.save
render "tags/show", status: :created
else
render json: #tag.errors, status: :unprocessable_entity
end
end
# Other methods...
private
# Use callbacks to share common setup or constraints between actions.
def set_tag
#tag = Tag.find_by(id: params[:id])
if !#tag
object_not_found
end
end
# Only allow a trusted parameter "white list" through.
def tag_params
params.require(:tag).permit(:name)
end
# render response for objects that aren't found
def object_not_found
render :json => {:error => "404 not found"}.to_json, status: :not_found
end
end
Request Spec
require 'rails_helper'
include AuthHelper
include Requests::JsonHelpers
RSpec.describe "Tags", type: :request do
before(:context) do
#user = create(:admin)
#headers = AuthHelper.authenticated_header(#user)
end
# A bunch of other specs...
describe "POST /api/tags" do
context "while authenticated" do
it "fails to create a tag from malformed body with 422 status" do
malformed_body = { "name": "malformed" }.to_json
post "/api/tags", params: malformed_body, headers: #headers
expect(response).to have_http_status(422)
expect(Tag.all.length).to eq 0
end
end
end
# A bunch of other specs...
after(:context) do
#user.destroy
#headers = nil
end
end
This behaviour is because of the ParamsWrapper functionality which is enabled by default in Rails 6. wrap_parameters wraps the parameters that are received, into a nested hash. Hence, this allows clients to send requests without nesting data in the root elements.
For example, in a model named Tag, it basically converts
{
name: "Some name",
age: "Some age"
}
to
{
tag:
{
name: "Some name",
age: "Some age"
}
}
However, as you see in your test, if you change the required key to not_tag, the wrapping breaks the API call, as expected.
This configuration can be changed using the config/initializers/wrap_parameters.rb file. In that file, you could set wrap_parameters format: [:json] to wrap_parameters format: [] to disallow such wrapping of parameters.

No route matches in functional/controller test

I have the following controller test using Minitest::Rails and Rails 4. When I run it, I get an error: ActionController::UrlGenerationError: No route matches {:action=>"/", :controller=>"foo"} error, despite it being defined.
The whole point of this is to test methods that are on ApplicationController (FooController exists only in this test and is not a placeholder for the question).
class FooController < ApplicationController
def index
render nothing: true
end
end
class FooControllerTest < ActionController::TestCase
it 'does something' do
with_routing do |set|
set.draw do
root to: 'foo#index', via: :get
end
root_path.must_equal '/' #=> 👍
get(root_path).must_be true #=> No route matches error
end
end
end
There a number of similar questions on StackOverflow and elsewhere, but they all refer to the issue of route segments being left out (e.g. no ID specified on a PUT). This is a simple GET with no params, however.
I get the same result if the route is assembled differently, so I don't think it's the root_path bit doing it (e.g. controller :foo { get 'test/index' => :index }).
I did some search on what information you have provided. I found an issue open in rspec-rails gem. However gem doesn't matter here but fundamentally they said its context problem. When you call with_routing it doesn't executed in correct context, so gives error of No Route matches.
To resolve issue, I tried locally with different solution. Here is what I have tried
require 'rails_helper'
RSpec.describe FooController, type: :controller do
it 'does something' do
Rails.application.routes.draw do
root to: 'foo#index', via: :get
end
expect(get: root_path).to route_to("foo#index")
end
end
In above code, the major problem is it overwrite existing routes. But we can reproduce routes with Rails.application.reload_routes! method.
I hope this helps to you!!
UPDTATE
I tried to understand your last comment and dig into get method. When we call get method it takes argument of action of controller for which we are doing test. In our case when we do get(root_path) it tries to find foo#/ which is not exists and hence gives no route matches error.
as our main goal is to check root_path routes are generated correctly, we need to use method assert_routing to check it. Here is how I test and it works
assert_routing root_path , controller: "foo", action: "index"
Full code :
require 'test_helper'
class FooControllerTest < ActionController::TestCase
it 'does something' do
with_routing do |set|
set.draw do
root to: 'foo#index', via: :get
end
root_path.must_equal '/' #=> true
assert_routing root_path , controller: "foo", action: "index" #=> true
get :index
response.body.must_equal ""
end
end
end
I read things from official document : http://api.rubyonrails.org/classes/ActionDispatch/Assertions/RoutingAssertions.html
if you check the source of ActionController::TestCase#get method, it expects action name, e.g. :index, :create, 'edit, 'create'
if you pass root_path on #get method, absolutely it will raise error, because root_path method returns '/'.
I just checked to add :/ method to FooController
class FooController
def index
end
def /
end
end
Rails.application.routes.draw do
root 'foo#index'
get 'foobar' => 'foo#/'
end
when I was visiting http://localhost:3000/foobar, rails gave me
AbstractController::ActionNotFound (The action '/' could not be found for FooController): respond
I think '/' is not permitted action on rails, I don't do research further, because I think it's very reasonable.
You may write
assert_routing '/', controller: "foo", action: "index"
for current test, then you can write integration test to check root_path and other features.
Following are the source code of some methods I've talked above: (I'm using rails version 4.2.3 to test this interesting issue)
action_controller/test_case.rb
# Simulate a GET request with the given parameters.
#
# - +action+: The controller action to call.
# - +parameters+: The HTTP parameters that you want to pass. This may
# be +nil+, a hash, or a string that is appropriately encoded
# (<tt>application/x-www-form-urlencoded</tt> or <tt>multipart/form-data</tt>).
# - +session+: A hash of parameters to store in the session. This may be +nil+.
# - +flash+: A hash of parameters to store in the flash. This may be +nil+.
#
# You can also simulate POST, PATCH, PUT, DELETE, and HEAD requests with
# +post+, +patch+, +put+, +delete+, and +head+.
#
# Note that the request method is not verified. The different methods are
# available to make the tests more expressive.
def get(action, *args)
process(action, "GET", *args)
end
# Simulate a HTTP request to +action+ by specifying request method,
# parameters and set/volley the response.
#
# - +action+: The controller action to call.
# - +http_method+: Request method used to send the http request. Possible values
# are +GET+, +POST+, +PATCH+, +PUT+, +DELETE+, +HEAD+. Defaults to +GET+.
# - +parameters+: The HTTP parameters. This may be +nil+, a hash, or a
# string that is appropriately encoded (+application/x-www-form-urlencoded+
# or +multipart/form-data+).
# - +session+: A hash of parameters to store in the session. This may be +nil+.
# - +flash+: A hash of parameters to store in the flash. This may be +nil+.
#
# Example calling +create+ action and sending two params:
#
# process :create, 'POST', user: { name: 'Gaurish Sharma', email: 'user#example.com' }
#
# Example sending parameters, +nil+ session and setting a flash message:
#
# process :view, 'GET', { id: 7 }, nil, { notice: 'This is flash message' }
#
# To simulate +GET+, +POST+, +PATCH+, +PUT+, +DELETE+ and +HEAD+ requests
# prefer using #get, #post, #patch, #put, #delete and #head methods
# respectively which will make tests more expressive.
#
# Note that the request method is not verified.
def process(action, http_method = 'GET', *args)
# .....
end
The whole point of this is to test behavior inherited by the ApplicationController.
There is no behavior in your question related to ApplicationController other than the fact that FooController inherits from ApplicationController. And since there's no other behavior to test here in FooController related to something from the Application Controller ...
You can test this
class FooController < ApplicationController
def index
render nothing: true
end
end
with this
describe FooController, type: :controller do
describe '#index' do
it 'does not render a template' do
get :index
expect(response).to render_template(nil)
end
end
end
The solution to this problem was much simpler than expected:
# change this:
get(root_path)
# to this
get(:index)
The with_routing method works fine to define the path in this context.

How to test an unrouted controller action or exception_app in Rails

We have a custom exception app that has been raising (fail safe) exceptions (the application equivalent of having an exception in a rescue block).
I think I've fixed it, but am finding it hard to test. It's an unrouted controller, so I can't use controller tests (require routing).
i.e. I have Rails.configuration.exceptions_app = ExceptionController.action(:show), not Rails.configuration.exceptions_app = self.routes.
Basically what I think I need to do is
Generate a test request request = ActionDispatch::TestRequest.new
include Rack::Test or maybe mimic behavior in ActiveSupport::IntegrationTest
Set #app = ExceptionsController.action(:show)
Fake an exception request.env.merge! 'action_dispatch.exception' => ActionController::RoutingError.new(:foo)
Test response = #app.call(request.env)
Assert no exception is raised and correct response body and status
Problems:
The env needs
a warden / devise session with current_user request.env['warden'] = spy(Warden) and request.session = ActionDispatch::Integration::Session.new(#app)
to manipulate request formats so that I can check that a request without an accept defaults to json request.any?(:json)? constraints: { default: :json } ? `request.accept = "application/javascript"
work work with the respond_with responder
set action_dispatch.show_exceptions, consider all requests local, etc request.env["action_dispatch.show_detailed_exceptions"] = true
Also, I considered building a ActionDispatch::ShowException.new(app, ExceptionController.new) or a small rack app
But our gem has no tests and I haven't been able to apply anything that I've read in exception handling gems (most work at the rescue_action_in_public level or mix in to ShowException) or in the Rails source code
This is a Rails 4.2 app tested via Rspec and Capybara.
Thoughts, links, halp?
Example code and tests
RSpec.describe 'ExceptionController' do
class ExceptionController < ActionController::Base
use ActionDispatch::ShowExceptions, Rails.configuration.exceptions_app
use ActionDispatch::DebugExceptions
#Response
respond_to :html, :json
#Layout
layout :layout_status
#Dependencies
before_action :status, :app_name, :log_exception
def show
respond_with details, status: #status, location: nil
end
def show_detailed_exceptions?
request.local?
end
protected
####################
# Dependencies #
####################
#Info
def status
#exception = env['action_dispatch.exception']
#status = ActionDispatch::ExceptionWrapper.new(env, #exception).status_code
#response = ActionDispatch::ExceptionWrapper.rescue_responses[#exception.class.name]
end
#Format
def details
#details ||= {}.tap do |h|
I18n.with_options scope: [:exception, :show, #response], exception_name: #exception.class.name, exception_message: #exception.message do |i18n|
h[:name] = i18n.t "#{#exception.class.name.underscore}.title", default: i18n.t(:title, default: #exception.class.name)
h[:message] = i18n.t "#{#exception.class.name.underscore}.description", default: i18n.t(:description, default: #exception.message)
end
end
end
helper_method :details
####################
# Layout #
####################
private
def log_exception
if #status.to_s == '500'
request.env[:exception_details] = details
request.env[:exception_details][:location] = ActionDispatch::ExceptionWrapper.new(env, #exception).application_trace[0]
end
end
#Layout
def layout_status
#status.to_s != '404' ? 'error' : 'application'
end
#App
def app_name
#app_name = Rails.application.class.parent_name
end
end
include Rack::Test::Methods
include ActionDispatch::Integration::Runner
include ActionController::TemplateAssertions
include ActionDispatch::Routing::UrlFor
let(:exception) { ActionController::RoutingError.new(:foo) }
let(:request) { ActionDispatch::TestRequest.new }
def app
# Rails.application.config.exceptions_app
#app ||= ExceptionController.action(:show)
end
it 'logs unknown format errors' do
request.env['action_dispatch.show_exceptions'] = true
request.env['consider_all_requests_local'] = true
request.env['warden'] = spy(Warden)
request.session = ActionDispatch::Integration::Session.new(app)
exception = ActionController::RoutingError.new(:foo)
request.env.merge! 'action_dispatch.exception' => exception
post '/whatever'
expect(response.body).to eq("dunno?")
end
end
refs:
https://github.com/richpeck/exception_handler
https://github.com/plataformatec/responders/blob/8f03848a2f50d4685c15a31254a1f600af947bd7/test/action_controller/respond_with_test.rb#L265-L275
https://github.com/rails/rails/blob/1d43458c148f9532a81b92ee3a247da4f1c0b7ad/actionpack/test/dispatch/show_exceptions_test.rb#L92-L99
https://github.com/rails/rails/blob/3e36db4406beea32772b1db1e9a16cc1e8aea14c/railties/test/application/middleware/exceptions_test.rb#L86-L91
https://github.com/rails/rails/blob/34fa6658dd1b779b21e586f01ee64c6f59ca1537/actionpack/lib/action_dispatch/testing/integration.rb#L647-L674
https://github.com/rails/rails/blob/ef8d09d932e36b0614905ea5bc3fb6af318b6ce2/actionview/test/abstract_unit.rb#L146-L184
https://github.com/plataformatec/responders/blob/8f03848a2f50d4685c15a31254a1f600af947bd7/lib/action_controller/respond_with.rb#L196-L207
http://blog.plataformatec.com.br/2012/01/my-five-favorite-hidden-features-in-rails-3-2/
https://github.com/bugsnag/bugsnag-ruby/blob/master/lib/bugsnag/middleware/warden_user.rb
https://github.com/bugsnag/bugsnag-ruby/blob/master/lib/bugsnag/rails/action_controller_rescue.rb#L3-L33
https://github.com/bugsnag/bugsnag-ruby/blob/master/lib/bugsnag/rails/active_record_rescue.rb
https://github.com/rollbar/rollbar-gem/blob/master/spec/rollbar_spec.rb
http://andre.arko.net/2011/12/10/make-rails-3-stop-trying-to-serve-html/
https://github.com/rails/rails/blob/9503e65b9718e4f01860cf017c1cdcdccdfffde7/actionpack/lib/action_dispatch/middleware/show_exceptions.rb#L46-L49
Update 2015-08-27
It has been suggested that this question may be a duplicate of Testing error pages in Rails with Rspec + Capybara, however, that question addresses testing exception responses when the exceptions_app is set to routes.
As I wrote above, I'm using a Controller as the exceptions_app, so though I could use capybara to visit non-existing pages, I'd like to test Controller's action directly, rather than include the rest of the show exceptions stack. This is important because my problem is when the exceptions app is called with an unhandled content type, which I cannot easily test via capybara.
More generally, what I need to test is when the Exceptions app raises and exception, that I have fixed it.
I'm open to seeing some example code, though.

Rails XML builder not rendering

I’m having an issue rendering XML in the response body of a request in a Rails 4 application. In the example below the response body is blank. I’ve put a debugger in the template so I know it runs through it but doesn’t render anything out.
I created a simple rails app to demonstrate the issue I’m having using builder to return xml. Can anyone point me to the (probably stupid simple) problem with this example?
Here are the controller, template, and test:
controllers/bars_controller.rb
require 'builder'
class BarsController < ApplicationController
before_action :set_bar, only: [:show]
# GET /bars/1
# GET /bars/1.json
def show
#xml = Builder::XmlMarkup.new
render template: 'bars/show.xml.builder', formats: [:xml]
end
private
# Use callbacks to share common setup or constraints between actions.
def set_bar
#bar = Bar.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def bar_params
params.require(:bar).permit(:foo, :bar)
end
end
/views/bars/show.xml.builder
#xml.instruct!
#xml.bar do
#xml.foo(#bar.foo)
#xml.bar(#bar.bar)
end
/test/controllers/bars_controller_test.rb
require 'test_helper'
class BarsControllerTest < ActionController::TestCase
setup do
#bar = bars(:one)
end
test "should show bar" do
get :show, id: #bar
assert_response :success
assert_match "<bar>", response.body
end
end
debugging session
1: #xml.instruct!
2: binding.pry
=> 3: #xml.bar do
4: #xml.foo(#bar.foo)
5: #xml.bar(#bar.bar)
6: end
[2] pry(#<#<Class:0x007fc669e9f610>>)> #xml.bar do
[2] pry(#<#<Class:0x007fc669e9f610>>)* #xml.foo(#bar.foo)
[2] pry(#<#<Class:0x007fc669e9f610>>)* #xml.bar(#bar.bar)
[2] pry(#<#<Class:0x007fc669e9f610>>)* end
=> "<?xml version=\"1.0\" encoding=\"UTF-8\"?><bar><foo>MyString</foo><bar>MyString</bar></bar>"
It looks like creating the instance of the Builder::XmlMarkup.new is your problem. Remove the explicit creation of the builder so your controller looks like this:
def show
# You can also simplify by removing "bars/"
render 'bars/show.xml.builder', formats: [:xml]
end
And your view should look like this:
xml.instruct!
xml.bar do
xml.foo(#bar.foo)
xml.bar(#bar.bar)
end

Testing instance variables in controller with RSpec

Given a controller like this where it creates several instance variables for use by the view, would you generally test that each of those get set properly? It seems like you would want to, but it also seems a little it could be a bit tricky. What's the right approach?
class StaffsController < ApplicationController
def index
set_index_vars
#all_staff = Staff.find_staff_for_business_all_inclusive(current_business_id)
respond_to do |format|
format.html { render :action => "index", :locals => { :all_staff => #all_staff, :all_services => #all_services, :new_vacation => #new_vacation } }
end
end
def set_index_vars
#days_of_week = days_of_week
#first_day_of_week = DefaultsConfig.first_day_of_week
#all_services = Service.services_for_business(current_business_id)
#new_vacation = StaffVacation.new
#has_hit_staff_limit = current_user_plan.has_hit_staff_limit?
end
end
The code is also posted at https://gist.github.com/1018190
If you're going to write a controller spec, then yes, by all means test that the instance variables are assigned. Much of the 'trickiness' can come from dependencies on other models/libraries, so stub out those method calls:
# air code
Staff.stub(:find_staff_for_business_all_inclusive) {array_of_staff}
controller.stub(:days_of_week) {['Monday','Tuesday',....etc...]}
DefaultsConfig.stub(:first_day_of_week) {"Monday"}
Service.stub(:services_for_business).with(some_value_for_the_current_business_id).\
and_return(some_relevant_value)
StaffVacation.stub(:new) {something_meaningful}
controller.stub_chain(:current_user_plan,:has_hit_staff_limit?) {false}
get :index
assigns(:days_of_week).should == ['Monday','Tuesday',....etc...]
# ...etc...
I would split it up as follows: test that the index calls the correct method. Then test whether the method works.
So something like
describe StaffsController do
describe "GET #index" do
it "calls set_index_vars" do
controller.should_receive(:set_index_vars)
get :index
end
# and your usual tests ...
end
describe "#set_index_vars" do
before(:each) do
# stub out the code not from this controller
controller.stub_chain(:current_user_plan, :has_hit_staff_limit?).and_return(false)
.. etc ..
controller.set_index_vars
end
it { assigns(:days_of_week).should == controller.days_of_week }
it { assigns(:has_hit_staff_limit).should be_false
# etc ..
end
end
Hope this helps.
So long as you have good coverage around your method, you can test that your method is being called at the right times, with the right values etc. Something like:
describe StaffsController do
describe "GET #index" do
it "should call set_index_vars" do
controller.should_receive(:set_index_vars)
get :index
end
end
describe "#set_index_vars" do
it "should assign instance variables with correct values" do
# or wtv this is supposed to do
get :index
assigns(:days_of_week).should == controller.days_of_week
# etc ..
end
end
end

Resources