how to test rails custom validation - ruby-on-rails-3

I have a custom validation that checks whether a param is valid JSON or not:
def is_valid_json
begin
!!JSON.parse(preferences)
rescue
errors.add(:preferences, "This is not valid JSON")
end
end
In my controller test, I want to make sure that when I send in a bad value, the status code of the response is 422. Here is the spec from my controller:
it 'should return a 422 when validations fail' do
put :update, {:user_preferences => { :email => #email, :preferences => 'badval' } }
expect(response.status).to eq(422)
res = JSON.parse(response.body)
expect(res['error']).to_not be_blank
end
The test fails due to an error:
Failure/Error: put :update, {:user_preferences => { :email => #email, :preferences => 'badval' } }
ActiveRecord::RecordInvalid:
Validation failed: Preferences This is not valid JSON
Controller code:
def update
#user_preference = UserPreference.where(email: params[:user_preferences][:email]).first
authorize! :update, #user_preference
#user_preference.update_attributes!(params[:user_preferences])
render_api_response(#user_preference)
end
When I make the request from the browser, I get a 422 return status code, so is there a reason that I can't get the same result from the test?

The way I see it, update_attributes raises an exception, and you need to catch that. Perhaps you are doing an XHR call with your browser and you code handles that exception code (422) in the front end. For tests to work you should rescue the exception and respond with the relevant status in your render
rescue ActiveRecord::RecordInvalid do
render json: {
error: "Invalid params",
status: 422
},
status: 422
end

Related

expected ActiveRecord::RecordNotFound but nothing was raised

How to get the test pass for this error?
Rspec result
**2) Api::V1::UsersController GET #show - a user it fails showing a user
Failure/Error:
expect do
get 'show', params: { id: 2 }
end.to raise_error(ActiveRecord::RecordNotFound)
expected ActiveRecord::RecordNotFound but nothing was raised
# ./spec/controllers/users_controller_spec.rb:100:in `block (3 levels) in <main>'
**
Controller -method
def show
begin
user = User.find(params[:id])
render json: UserSerializer.new(user).serialized_json
rescue ActiveRecord::RecordNotFound => e
render json: { error: e.to_s }, status: :not_found
end
end
**
Rspec controller
it 'it fails showing a user' do
expect do
get 'show', params: { id: 2 }
end.to raise_error(ActiveRecord::RecordNotFound)
end
I could solve it in this way,
def show
user = User.find(params[:id])
begin
render json: UserSerializer.new(user).serialized_json
rescue ActiveRecord::RecordNotFound => e
render json: { error: e.to_s }, status: :not_found
end
end

Stubbing out save methodin rails with rspec

I have the following action in my controller:
def create
#user = current_user
#vin = #user.vins.new(params[:vin])
if #vin.save
# waiting for implementation
logger.debug("here we are")
else
redirect_to(vins_path)
end
end
I'd like to test with with rspec. However, I want to stub out the save operation to simulate a failure:
it "should send user to vins_path if there is a problem creating the VIN" do
#vin.stub!(:save).and_return(false)
post 'create', :vin => { :name => "Test 1", :vin => "test" }
response.should redirect_to(vins_path)
end
However, the stub doesn't seem to work as the save operation is always successful. What am I doing wrong?
Thanks
Try this:
Vin.any_instance.stub(:save).and_return(false)

Forcing 404 error in the right way in Rails [duplicate]

I'd like to 'fake' a 404 page in Rails. In PHP, I would just send a header with the error code as such:
header("HTTP/1.0 404 Not Found");
How is that done with Rails?
Don't render 404 yourself, there's no reason to; Rails has this functionality built in already. If you want to show a 404 page, create a render_404 method (or not_found as I called it) in ApplicationController like this:
def not_found
raise ActionController::RoutingError.new('Not Found')
end
Rails also handles AbstractController::ActionNotFound, and ActiveRecord::RecordNotFound the same way.
This does two things better:
1) It uses Rails' built in rescue_from handler to render the 404 page, and
2) it interrupts the execution of your code, letting you do nice things like:
user = User.find_by_email(params[:email]) or not_found
user.do_something!
without having to write ugly conditional statements.
As a bonus, it's also super easy to handle in tests. For example, in an rspec integration test:
# RSpec 1
lambda {
visit '/something/you/want/to/404'
}.should raise_error(ActionController::RoutingError)
# RSpec 2+
expect {
get '/something/you/want/to/404'
}.to raise_error(ActionController::RoutingError)
And minitest:
assert_raises(ActionController::RoutingError) do
get '/something/you/want/to/404'
end
OR refer more info from Rails render 404 not found from a controller action
HTTP 404 Status
To return a 404 header, just use the :status option for the render method.
def action
# here the code
render :status => 404
end
If you want to render the standard 404 page you can extract the feature in a method.
def render_404
respond_to do |format|
format.html { render :file => "#{Rails.root}/public/404", :layout => false, :status => :not_found }
format.xml { head :not_found }
format.any { head :not_found }
end
end
and call it in your action
def action
# here the code
render_404
end
If you want the action to render the error page and stop, simply use a return statement.
def action
render_404 and return if params[:something].blank?
# here the code that will never be executed
end
ActiveRecord and HTTP 404
Also remember that Rails rescues some ActiveRecord errors, such as the ActiveRecord::RecordNotFound displaying the 404 error page.
It means you don't need to rescue this action yourself
def show
user = User.find(params[:id])
end
User.find raises an ActiveRecord::RecordNotFound when the user doesn't exist. This is a very powerful feature. Look at the following code
def show
user = User.find_by_email(params[:email]) or raise("not found")
# ...
end
You can simplify it by delegating to Rails the check. Simply use the bang version.
def show
user = User.find_by_email!(params[:email])
# ...
end
The newly Selected answer submitted by Steven Soroka is close, but not complete. The test itself hides the fact that this is not returning a true 404 - it's returning a status of 200 - "success". The original answer was closer, but attempted to render the layout as if no failure had occurred. This fixes everything:
render :text => 'Not Found', :status => '404'
Here's a typical test set of mine for something I expect to return 404, using RSpec and Shoulda matchers:
describe "user view" do
before do
get :show, :id => 'nonsense'
end
it { should_not assign_to :user }
it { should respond_with :not_found }
it { should respond_with_content_type :html }
it { should_not render_template :show }
it { should_not render_with_layout }
it { should_not set_the_flash }
end
This healthy paranoia allowed me to spot the content-type mismatch when everything else looked peachy :) I check for all these elements: assigned variables, response code, response content type, template rendered, layout rendered, flash messages.
I'll skip the content type check on applications that are strictly html...sometimes. After all, "a skeptic checks ALL the drawers" :)
http://dilbert.com/strips/comic/1998-01-20/
FYI: I don't recommend testing for things that are happening in the controller, ie "should_raise". What you care about is the output. My tests above allowed me to try various solutions, and the tests remain the same whether the solution is raising an exception, special rendering, etc.
You could also use the render file:
render file: "#{Rails.root}/public/404.html", layout: false, status: 404
Where you can choose to use the layout or not.
Another option is to use the Exceptions to control it:
raise ActiveRecord::RecordNotFound, "Record not found."
The selected answer doesn't work in Rails 3.1+ as the error handler was moved to a middleware (see github issue).
Here's the solution I found which I'm pretty happy with.
In ApplicationController:
unless Rails.application.config.consider_all_requests_local
rescue_from Exception, with: :handle_exception
end
def not_found
raise ActionController::RoutingError.new('Not Found')
end
def handle_exception(exception=nil)
if exception
logger = Logger.new(STDOUT)
logger.debug "Exception Message: #{exception.message} \n"
logger.debug "Exception Class: #{exception.class} \n"
logger.debug "Exception Backtrace: \n"
logger.debug exception.backtrace.join("\n")
if [ActionController::RoutingError, ActionController::UnknownController, ActionController::UnknownAction].include?(exception.class)
return render_404
else
return render_500
end
end
end
def render_404
respond_to do |format|
format.html { render template: 'errors/not_found', layout: 'layouts/application', status: 404 }
format.all { render nothing: true, status: 404 }
end
end
def render_500
respond_to do |format|
format.html { render template: 'errors/internal_server_error', layout: 'layouts/application', status: 500 }
format.all { render nothing: true, status: 500}
end
end
and in application.rb:
config.after_initialize do |app|
app.routes.append{ match '*a', :to => 'application#not_found' } unless config.consider_all_requests_local
end
And in my resources (show, edit, update, delete):
#resource = Resource.find(params[:id]) or not_found
This could certainly be improved, but at least, I have different views for not_found and internal_error without overriding core Rails functions.
these will help you...
Application Controller
class ApplicationController < ActionController::Base
protect_from_forgery
unless Rails.application.config.consider_all_requests_local
rescue_from ActionController::RoutingError, ActionController::UnknownController, ::AbstractController::ActionNotFound, ActiveRecord::RecordNotFound, with: lambda { |exception| render_error 404, exception }
end
private
def render_error(status, exception)
Rails.logger.error status.to_s + " " + exception.message.to_s
Rails.logger.error exception.backtrace.join("\n")
respond_to do |format|
format.html { render template: "errors/error_#{status}",status: status }
format.all { render nothing: true, status: status }
end
end
end
Errors controller
class ErrorsController < ApplicationController
def error_404
#not_found_path = params[:not_found]
end
end
views/errors/error_404.html.haml
.site
.services-page
.error-template
%h1
Oops!
%h2
404 Not Found
.error-details
Sorry, an error has occured, Requested page not found!
You tried to access '#{#not_found_path}', which is not a valid page.
.error-actions
%a.button_simple_orange.btn.btn-primary.btn-lg{href: root_path}
%span.glyphicon.glyphicon-home
Take Me Home
routes.rb
get '*unmatched_route', to: 'main#not_found'
main_controller.rb
def not_found
render :file => "#{Rails.root}/public/404.html", :status => 404, :layout => false
end
<%= render file: 'public/404', status: 404, formats: [:html] %>
just add this to the page you want to render to the 404 error page and you are done.
I wanted to throw a 'normal' 404 for any logged in user that isn't an admin, so I ended up writing something like this in Rails 5:
class AdminController < ApplicationController
before_action :blackhole_admin
private
def blackhole_admin
return if current_user.admin?
raise ActionController::RoutingError, 'Not Found'
rescue ActionController::RoutingError
render file: "#{Rails.root}/public/404", layout: false, status: :not_found
end
end
Raising ActionController::RoutingError('not found') has always felt a little bit strange to me - in the case of an unauthenticated user, this error does not reflect reality - the route was found, the user is just not authenticated.
I happened across config.action_dispatch.rescue_responses and I think in some cases this is a more elegant solution to the stated problem:
# application.rb
config.action_dispatch.rescue_responses = {
'UnauthenticatedError' => :not_found
}
# my_controller.rb
before_action :verify_user_authentication
def verify_user_authentication
raise UnauthenticatedError if !user_authenticated?
end
What's nice about this approach is:
It hooks into the existing error handling middleware like a normal ActionController::RoutingError, but you get a more meaningful error message in dev environments
It will correctly set the status to whatever you specify in the rescue_responses hash (in this case 404 - not_found)
You don't have to write a not_found method that needs to be available everywhere.
To test the error handling, you can do something like this:
feature ErrorHandling do
before do
Rails.application.config.consider_all_requests_local = false
Rails.application.config.action_dispatch.show_exceptions = true
end
scenario 'renders not_found template' do
visit '/blah'
expect(page).to have_content "The page you were looking for doesn't exist."
end
end
If you want to handle different 404s in different ways, consider catching them in your controllers. This will allow you to do things like tracking the number of 404s generated by different user groups, have support interact with users to find out what went wrong / what part of the user experience might need tweaking, do A/B testing, etc.
I have here placed the base logic in ApplicationController, but it can also be placed in more specific controllers, to have special logic only for one controller.
The reason I am using an if with ENV['RESCUE_404'], is so I can test the raising of AR::RecordNotFound in isolation. In tests, I can set this ENV var to false, and my rescue_from would not fire. This way I can test the raising separate from the conditional 404 logic.
class ApplicationController < ActionController::Base
rescue_from ActiveRecord::RecordNotFound, with: :conditional_404_redirect if ENV['RESCUE_404']
private
def conditional_404_redirect
track_404(#current_user)
if #current_user.present?
redirect_to_user_home
else
redirect_to_front
end
end
end

RSpec and weird tests results

I'm trying to make a simple app. When Im testing it in browser everytyhing works just fine. Howerver, when I try to run some tests with RSpec (2.5) it fails when it comes to :create test for controller.
Here's my create method:
def create
#website = Website.new(params[:website])
if #website.save
flash[:notice] = "Website created."
redirect_to(:action => 'list')
else
render('new')
end
end
The controller test:
describe WebsitesController do
render_views
.
.
.
describe "POST 'create'" do
before(:each) do
#attr = { :adres => "www.excc.pl", :opis => "aaa "*22, :tagi => "aaa aaa aaa",
:preview => File.new(Rails.root + 'spec/fixtures/rails.png'),
:preview_mini => File.new(Rails.root + 'spec/fixtures/rails.png')}
end
describe "success" do
it "should have the right title" do
response.should have_selector("title", :content=>"Lista witryn w portfolio")
end
end
.
.
.
The result of this test:
1) WebsitesController POST 'create' should have the right title
Failure/Error: response.should have_selector("title", :content=>"Lista witryn w portfolio")
expected following output to contain a <title>Lista witryn w portfolio</title> tag:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
# ./spec/controllers/websites_controller_spec.rb:34:in `block (4 levels) in
websites_controller_spec.rb:34 refers to create method
However, this test is passed correctly (for incorrect data it should be redirected back to 'new' site with specified title):
it "should have the right title" do
post :create, :website => #attr.merge(:adres => "")
response.should have_selector("title", :content=>"Dodaj stronę WWW")
end
The second problem is...
There was a time when I've got a test result like this:
<html><body>You are being redire cted.</body></html>
... which was causing me to pull my hair out for some time until I've done sth (I don't really know what) and it was gone. Yet, it makes me scared like hell when I think that it can come back in future an ruin my happiness.
Any thoughts on this would be greatly appreciated.
It's hard to know what is being asked here, but I believe the issue is that you are not setting the conditions for success/failure. If I understand correctly, when you pass in an blank :adres attribute, the save should fail and the page should render the list action. So you want to stub the create method and return true or false depending on the expected result:
it "succeeds" do
#website = mock_model(Website,:save=>true)
Website.stub(:new) { #website }
post :create, :website => {}
# redirects
response.should have_selector("etc etc")
end
it "fails" do
#website = mock_model(Website,:save=>false)
Website.stub(:new) { #website }
post :create, :website => {}
# renders 'new'
response.should_not have_selector("etc etc")
end
Testing of the validity of the parameters should be performed in the model spec:
#website = Website.new(:adres=>"")
#website.should_not be_valid

RSpec Newbie: "Update attributes => false" not being recognised

Just starting out with RSpec. Everything is going smoothly, except for one spec with nested controllers.
I'm trying to ensure that when a 'comment' resource (nested under 'post') is updated with invalid parameters, it renders the 'edit' template. I'm struggling to get rspec to recognise the :update_attributes => false trigger. If anyone has any suggestions, they'd be very appreciated. Attempted code below:
def mock_comment(stubs={})
stubs[:post] = return_post
stubs[:user] = return_user
#mock_comment ||= mock_model(Comment, stubs).as_null_object
end
describe "with invalid paramters" dog
it "re-renders the 'edit' template" do
Comment.stub(:find).with("12") { mock_comment(:update_attributes => false) }
put :update, :post_id => mock_comment.post.id, :id => "12"
response.should render_template("edit")
end
end
And the controller:
def update
#comment = Comment.find(params[:id])
respond_to do |format|
if #comment.update_attributes(params[:comment])
flash[:notice] = 'Post successfully updated'
format.html { redirect_to(#comment.post) }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => #comment.errors, :status => :unprocessable_entity }
end
end
end
And finally, the error:
Failure/Error: response.should render_template("edit")
expecting <"edit"> but rendering with <"">.
Expected block to return true value.
This is quite an interesting problem. A quick fix is to simply replace the block form of Comment.stub:
Comment.stub(:find).with("12") { mock_comment(:update_attributes => false) }
with an explicit and_return:
Comment.stub(:find).with("12").\
and_return(mock_comment(:update_attributes => false))
As to why these two forms should produce different results, that's a bit of a head-scratcher. If you play around with the first form you'll see that the mock is actually returning self instead of false when the stubbed method is called. That's tells us it hasn't stubbed the method (since it's specified as a null object).
The answer is that when passing in a block, the block is only executed when the stubbed method is called, not when the stub is defined. So when using the block form, the following call:
put :update, :post_id => mock_comment.post.id, :id => "12"
is executing mock_comment for the first time. Since :update_attributes => false is not being passed in, the method is not stubbed, and the mock is returned rather than false. When the block invokes mock_comment it returns #mock_comment, which doesn't have the stub.
Contrariwise, using the explicit form of and_return invokes mock_comment immediately. It would probably be better to use the instance variable instead of calling the method each time to make the intent clearer:
it "re-renders the 'edit' template" do
mock_comment(:update_attributes => false)
Comment.stub(:find).with("12") { #mock_comment }
put :update, :post_id => #mock_comment.post.id, :id => "12"
response.should render_template("edit")
end