I had previously had this in my controller specs:
post :operations, '1': @params, border: [0, 0, '#ffffff'], shave!: [0, 0], format: :json
But that fails to send the content as JSON anymore and I attributed that to the changed behaviour in rails for keyword arguments. So I changed that to this:
post :operations, params: { '1' => @params, 'border' => [0, 0, '#ffffff'], 'shave!' => [0, 0] }, headers: { 'Content-Type' => 'application/json' }
But when I inspect for headers, I still get this:
(byebug) headers
{"X-Frame-Options"=>"SAMEORIGIN", "X-XSS-Protection"=>"1; mode=block", "X-Content-Type-Options"=>"nosniff"}
Is this an issue with rspec?
This is a thin wrapper over the Rails helpers, what happens if you leave the params : { .. } but return to format: :json, the big change in Rails 5 was the need to pass params as a hash.
Where are you inspecting headers?
I am inspecting headers in the byebug console. The problem is that Rails is now stringifying all my params (even non-string types) and setting format: :json or the Content-Type explicitly doesn't handle this. I have multiple specs failing because of this even though the main app works fine.
Sorry, by inspecting headers, I meant where in your request cycle, e.g. where you are triggering byebug from.
All params are strings from a request perspective, are you attempting to test Rails 5 here? Is this an old controller spec? Have you installed rails-controller-testing?
Need more information to help :)
I triggered byebug from within the controller action I was testing(operations).
rails-controller-testing is indeed included in the :test group.
This is an old controller spec in the sense that we tested stuff on 4.2 with it. I am in the process of migrating to Rails 5 and the app runs fine but the tests fail. I have followed the multiple guides scattered around ( from Michael Scheenman and the official Rails Guides, for instance) for the upgrade path.
I am aware that all params are strings from the request perspective, but rails does a good job of converting a JSON payload to a Hash while preserving its types. This was also a problem as described in #610 but the solution no longer works.
This might be an issue to raise with the controller testing gem, I know theres been some load order issues though, did you try the require: false trick mentioned elsewhere?
This _might_ be us, we did change some of our interaction with the test request generation in 3.5.0
Considering it is an issue with you rspec-rails, is there a short term fix I can apply now or should I wait for the next release?
I think the issue is easy to reproduce and if you guys want, I can spin up a small repro app.
@pawandubey we don't know if this is an issue with rspec-rails yet
I can confirm this issue, and it looks like a problem with RSpec and the way Rails changed controller tests.
Here's my controller action:
def create
@widget = Widget.new(widget_params)
@boolean_attr = params[:boolean_attr]
respond_to do |format|
...
end
end
This TestUnit test passes:
test "should have access to boolean attributes in JSON request" do
post widgets_url, params: { widget: { }, boolean_attr: true }, as: :json
assert_same(assigns(:boolean_attr), true)
end
This RSpec spec fails:
it 'has access to boolean attributes in JSON request' do
post :create, params: { widget: { }, boolean_attr: true }, as: :json
expect(assigns(:boolean_attr)).to be true
end
(I'm on rails 5.0.0.1, rspec-rails 3.5.1, and rails-controller-testing 1.0.1)
Rails controller specs now inherit from ActionDispatch::IntegrationTest instead of ActionController::TestCase. But RSpec controller specs still use ActionController::TestCase which is deprecated.
When I step through in TestUnit, I get here. It's creating an encoder for the format given by the as attribute, in my case, :json.
But in RSpec, we end up here instead, and it appears that nobody has set the CONTENT_TYPE header at this point.
Here is a workaround that makes my spec pass:
it 'has access to boolean attributes in JSON request' do
request.content_type = 'application/json'
post :create, params: { widget: { }, boolean_attr: true }
expect(assigns(:boolean_attr)).to be true
end
Is RSpec going to follow along with Rails and move to integration tests instead of controller tests?
Heavily inspired by @alexsanford鈥檚 comment above, I made a script that uses Minitest and RSpec to test against the same controller code: https://gist.github.com/bquorning/17380163dd0c1aee9dfac10a9495b628
One of the controller specs uses the workaround setting request.controller_type = "application/json" and passes. The other one doesn鈥檛.
Both controller specs should probably use as: :json instead of format: :json, but I believe this has to do with the ActionController::TestCase/ActionDispatch::IntegrationTest switch as well.
@JonRowe any update on this issue? I am upgrading to Rails 5 and can currently use request.content_type = 'application/json'. Rails itself addressed this issue here: https://github.com/rails/rails/pull/26212 and it definitely looks like the problem is because rspec continues to use ActionController::TestCase
@danReynolds sounds like you have a handle on it, will happily review a PR if you think you've found a cause within RSpec.
@JonRowe I've quickly skimmed through rspec-rails and I don't think there's an issue within the gem, the problem was in ActionController::TestCase. And that issue was fixed in rails/rails#26212.
What's the status of this issue, it looks like this was fixed upstream in Rails 5.1.0
@mrageh It was fixed and even backported to 5.0, but I still get string types for float values in my controller tests using rails 5.0.1 (upgrading to 5.1 isn't an option right now).
Closing as this issue is upstream and has been fixed, hopefully they'll release a 5.0.2 for you @pawandubey
as a workaround for Rails 5.0.x, I'd suggest to use something like that:
def permitted_params
required_params = params.permit(:boolean_attr, :other_params).to_h
if required_params.has_key?(:boolean_attr)
required_params[:boolean_attr] = ActiveModel::Type::Boolean.new.cast(required_params[:boolean_attr])
end
required_params
end
Most helpful comment
I can confirm this issue, and it looks like a problem with RSpec and the way Rails changed controller tests.
Here's my controller action:
This TestUnit test passes:
This RSpec spec fails:
(I'm on rails 5.0.0.1, rspec-rails 3.5.1, and rails-controller-testing 1.0.1)
Here is the problem:
Rails controller specs now inherit from
ActionDispatch::IntegrationTestinstead ofActionController::TestCase. But RSpec controller specs still useActionController::TestCasewhich is deprecated.When I step through in TestUnit, I get here. It's creating an encoder for the format given by the
asattribute, in my case,:json.But in RSpec, we end up here instead, and it appears that nobody has set the
CONTENT_TYPEheader at this point.Here is a workaround that makes my spec pass:
Is RSpec going to follow along with Rails and move to integration tests instead of controller tests?