rspec/spec/controllers/javascript_controller_spec.rb
describe '#show' do
context 'when the javascript can be found' do
subject { get :show, params: { id: javascript.id } }
it { is_expected.to have_http_status(:ok) }
it 'returns the correct body' do
expect(subject.body).to eq(javascript.to_json)
end
end
context 'When the javascript can\'t be found' do
subject { get :show, params: { id: 'blahdeblah' } }
it { is_expected.to have_http_status(:not_found)}
it 'returns an error' do
expect(subject.body).to eq("{\"code\":404,\"message\":\"Javascript with id 'blahdeblah' not found\"}")
end
end
end
controllers/javascript_controller
class JavascriptController < ApplicationController
# ...
def show
javascript = Javascript.find(params[:id])
javascript_hash = Rails.cache.fetch(javascript.cache_key) {javascript.as_json }
render json: javascript_hash, status: :ok
end
# ...
end
So my first two test are passing but my last two aren't.
I'm getting the error 1) JavascriptController#show When the javascript
can't be found returns an error
Failure/Error: javascript = Javascript.find(params[:id])
ActiveRecord::RecordNotFound:
Couldn't find Javascript with 'id'=blahdeblah
its throwing two of the same errors actually for the last two. I'm trying to test for if the ID is not in the DB then throw an error for not found. Can anyone help me with this issue?
You're writing a controller spec, which is a relatively low-level spec: it will invoke your controller action, but any exception raised from there (like ActiveRecord::RecordNotFound) will bubble straight up into your spec.
To see a more comprehensive view of what will happen with real requests, use a request spec instead. That will pass its request through the full stack, and the exception will then be turned into a 404 response of some sort, as it will in production.
Sorry to say this but RTFM. .find throws RecordNotFound if the "id" cant be found. Your controller is raising an error before it gets to the render line.
In short, the test isn't broken. Your controller doesn't conform to the tests that are written in the test suite so you need to update your controller code so that the test will pass. This is basic Test Driven Development (TDD). Update your controller code so all the tests pass.
Related
I have a not-null constraint on a column. I have an IntegrationTest test case that posts an instance without that column. My goal is to assert that response code returned is 400.
My first try was:
test "should fail if no context" do
post trips_url, params: { trip: { state: #trip.state } }
assert_response 400
end
But this fails with Minitest::UnexpectedError: ActiveRecord::NotNullViolation: SQLite3::ConstraintException: NOT NULL constraint failed: trips.context:....
Which I don't really understand. Isn't post something like an HTTP client, and so it shouldn't be affected by the exceptions thrown in the controller?
But anyway, next I tried:
test "should fail if no context" do
assert_raises(ActiveRecord::NotNullViolation) do
post trips_url, params: { trip: { state: #trip.state } }
end
assert_response 400
end
But this fails with Minitest::UnexpectedError: NoMethodError: undefined method `response_code' for nil:NilClass, as if response_code wasn't set since an exception is thrown.
So, my question: How to achieve my goal? When testing an endpoint, I don't really care about what exceptions were thrown; I care about what response it's returned. So, how to assert on the response in the case of a failure?
I'm using Rails 5.1.4.
Depends on if your controller is rescuing the ActiveRecord::NotNullViolation and rendering the 400 ? I imagine controller and spec to be something like below
Controller code
class User < ApplicationController
def create
.....
rescue ActiveRecord::NotNullViolation => e
render text: e.message, status: :bad_request
end
end
Spec code
it 'POST with null data returns bad request' do
post to/user/path
expect(response.code).to eq '400'
end
I am trying to create an RSpec test which detects if a request can crash the controller, usually a 500 error. So I want to be able to distinguish between:
nil.invalid_method # raises NoMethodError
from
params.require(:required_parameter) # raises ActionController::ParameterMissing
in a controller in a generic way. When I do a request,feature or controller test it raises an exception:
describe "Post", type: :request do
it 'does not crash when no params given' do
post '/posts' # this line launches an exception
expect(page).to_not have_http_status(500)
end
end
It seems that before RSpec (or Rails I don't know) had a different behaviour, similar to I'm looking for:
rails 4 api rspec test http status code 410
Rspec shows different status code than browser
How to use HTTP status code symbols in RSpec?
How can I do this? Or how would you do?
Thanks for your time.
You can use a controller spec that doesn't render a 500, but raises the exception instead:
describe "PostController", type: :controller do
describe "POST index" do
it 'does not crash with valid params' do
expect {
post :index, { post: { title: 'foo' } }
}.to_not raise_exception
end
end
describe "POST index" do
it 'crashes without params' do
expect {
post :index
}.to raise_exception(ActionController::ParameterMissing)
end
end
end
Also note the curly brackets { ... } after expect.
You can test that the controller does not raise an uncaught exception by using the raise_error matcher:
RSpec.describe "Things", type: :request do
describe "POST /things" do
it "does not raise an error" do
# we pass a block to expect
expect { post things_path }.to_not raise_error
end
end
end
If the exception is rescued in the controller by using the rescue keyword or Rails rescue_from you would test the response code as usual:
class ThingsController < ApplicationController
rescue_from ActionController::ParameterMissing do
head 500
end
def create
raise ActionController::ParameterMissing.new('foo')
end
end
RSpec.describe "Things", type: :request do
describe "POST /things" do
it "work even if the param is not provided" do
post things_path
expect(response).to successful
end
end
end
In this case it is much more useful to test that the response is what you expected it to be - not that it is not a 500.
Is it possible to examine if get request rendered text?
I know there are hacks like response.body == 'any string' but it does not interest me. I'm just wondering if there is "RSpecâ„¢" way to do it.
Having this rspec:
RSpec.describe MyController, type: :controller do
controller do
def index
render text: 'Hellow'
end
end
describe 'rendering' do
subject { get :index }
it { is_expected.to render_template(text: 'Hellow')}
end
end
I would love to be able to call it { is_expected.to render_template(text: 'Hellow')}. It raises:
Failure/Error: it { is_expected.to render_template(text: 'Hellow') }
ArgumentError:
Unknown key: :text. Valid keys are: :layout, :partial, :locals, :count, :file
or maybe it { is_expected.to render_template('Hellow')}
Failure/Error: it { is_expected.to render_template('Hellow') }
expecting <"Hellow"> but rendering with <[]>
Is there any RSpecâ„¢ way to accomplish it?
Testing expect(response.body).to eq('Hellow') is totally appropriate.
The reason is_expected.to render_template isn't working is you aren't rendering a template. If your controller omitted an explicit render call, Rails would render the index template for you, and you could test render_template(:index). You could also render template: :foo and then test render_template(:foo) if you wanted to render a nonstandard template. But when you render text: 'Hellow', you aren't using templates; you're explicitly setting the response body to the text you specify.
If you do render a template, and you want to test the content rendered by that template, that's when render_views comes into play, as gotva mentioned. Even then, you'd be checking for content in response.body, as you can see in RSpec's own examples. As your templates get complicated, the controller specs aren't the appropriate place for this and you should start writing view specs using assert_select or something similar.
My app's view specs started failing because of a change we introduced to version control and I'm trying to debug.
Essentially, we had a ton of view specs that always passed, and now many, but not all, of them produce ActionController::UnknownFormat errors in the controller. Here is an example of a method that is blowing up:
def show
#user = current_user
FooCountItem.increment_requests
respond_to do |format|
format.html
format.json { render json: #foo }
end
end
Here's an example of spec that just began to fail:
describe "show.html.erb", type: :feature do
context "as admin" do
let!(:foo) { FactoryGirl.create(:foo) }
let!(:user) { FactoryGirl.create(:admin_user) }
before :each do
page.set_rack_session(user_id: user.id)
visit data_service_path(foo.id)
end
it "displays the foo's name" do
expect(page).to have_content(foo.name)
end
end
end
How is it possible that html is not the expected response? How could the controller react as if it has to produce a different format?
Thanks.
Updated: I'm also seeing NoMethodError: undefined method 'symbolize_keys' for "":String on some lines that are doing the controller requests from the controller_specs. I have no changes in spec_helper.rb when compared to working version of the test suite.
If I add format: :html to a call in the controller spec, it passes. I must have the app globally configured to respond to :js.
I am having trouble for the matcher to catch the exception. The controller method simply doing a REST call and get the fruit with the id and I want to test when REST give me an error respond which in rails is the JSON::ParserError. I want to test this case, so I stub out the REST call and raise the exception.
I know the fact that the stubbing work since I am getting that exact error. I believe that I just need a matcher to catch the error when calling the get
In Controller
def show
#fruit = FruitsService::Client.get_fruit(params[:id])
end
spec/controller/fruits_controller_spec.rb
describe '#show' do
before do
context 'when a wrong id is given' do
FruitsService::Client.any_instance
.stub(:get_fruit).with('wrong_id')
.and_raise(JSON::ParserError)
end
it 'receives 404 error code' do
get :show, {id: 'wrong_id'} <------ I think I might need a matcher for this ?
expect(FruitsService::Client.get_fruit('wrong_id')).to raise_error(JSON::ParserError)
end
end
This giving this
Failure/Error: get :show, {id: 'wrong_id'}
JSON::ParserError:
JSON::ParserError
When you want to test behavior, such as the raising of errors, you need to pass a block to expect instead of a parameter, as in:
it 'receives 404 error code' do
expect { get :show, {id: 'wrong_id'} }.to raise_error(JSON::ParserError)
end