I'm having trouble testing for a thrown AASM error.
Here is my controller method:
# controllers/jobs_controller.rb
def change_state
respond_to do |format|
if #job.close && #job.save
format.html { redirect_to #job, notice: 'Job has been closed.' }
format.json { render :show, status: :ok, location: #job }
else
format.html { render :show, notice: 'Job could not be closed.' }
format.json { render json: #job.errors, status: :unprocessable_entity }
end
end
end
My spec looks like this:
# spec/controllers/jobs_controller_spec.rb
describe "POST #change_state" do
it "cannot transition job from closed" do
job.aasm_state = "closed"
job.save!
post :change_state, params: {id: job.hash_id, user_id: user.id}
expect { response }.to raise_error(AASM::InvalidTransition)
end
end
The test fails (with the expected/desired failure):
Failure/Error: if #job.close && #job.save
AASM::InvalidTransition:Event 'close' cannot transition from 'closed'.
I'm just unable to figure out the right syntax to pass the test. I've tried a few variations of the expect line but can't seem to piece it together.
Any guidance is appreciated.
The exception is happening before the expect statement. Try it:
expect {
post(:change_state, params: { id: job.hash_id, user_id: user.id })
}.to(
raise_error(AASM::InvalidTransition)
)
Related
For my user controller, while running the test cases I am facing render issue. It is redirecting to http://test.host/sign_in instead of rendering new.
Controller code
def create
#user = User.new(user_params)
respond_to do |format|
if #user.save
UserMailer.registration_confirmation(#user).deliver_now
session[:user_id] = #user.id
format.html { redirect_to sign_in_path, notice: 'User was successfully created.' }
format.json { render :show, status: :created, location: #user }
else
format.html { render :new }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
spec/users_controller_spec.rb
describe '#create' do
it 'User created' do
new_user = FactoryBot.build :user
post :create, params: {
user: {
id: new_user.id,
firstname: new_user.firstname,
email: new_user.email,
password: "s",
password_confirmation:"s"
}
}
expect(response).to render_template("new")
end
end
While running this test case I am facing this error!
Error
Failures:
1) UsersController controller test cases #create User created
Failure/Error: expect(response).to render_template("new")
expecting <"new"> but was a redirect to <http://test.host/sign_in>
# ./spec/controllers/users_controller_spec.rb:30:in `block (4 levels) in <top (required)>'
How do I resolve this?
According to your controller code, you redirect to sign_in when the save is successful :
format.html { redirect_to sign_in_path, notice: 'User was successfully created.' }
Your failing test is saying :
expecting <"new"> but was a redirect to <http://test.host/sign_in>
It comes from expect(response).to render_template("new"). You might want to change this line to expect(subject).to redirect_to(sign_in_path).
The behaviour render_template("new") might be relevant when #user.save is false
respond_to not working inside controller rescue block:
def create
respond_to do | format |
format.json { render json: SomeManager.new(some_params).json }
format.html do
SomeManager.new(some_params)
render :new, notice: 'it worked'
end
end
rescue => e
respond_to do | format |
format.json { render json: {error: 'did not work because reasons'}.to_json, status: :forbidden }
format.html { render :new, alert: 'did not work because reasons' }
end
end
^ Controller for API which can respond to API json uploads, or manual uploads using UI.
For better readability, use begin .. rescue like this instead:
def create
respond_to do | format |
begin
format.json { render json: SomeManager.new(some_params).json }
format.html do
SomeManager.new(some_params)
render :new, notice: 'it worked'
end
rescue => e
format.json { render json: {error: 'did not work because reasons'}.to_json, status: :forbidden }
format.html { render :new, alert: 'did not work because reasons' }
end
end
end
I think it shall work.
The rescue block isn't to blame. This would happen if your routes.rb is specified with an option like so:
resources :widgets, defaults: {format: :the_spanish_inquisition}
In my rails app, I want the user to be able to select an option in a 'new' form, but if the option already exists, I want it to update the current option.
I have this so far in my create method:
def create
#cost = Cost.new(cost_params)
if Cost.exists?(:category => #cost.category, :option => #cost.option)
redirect_to action: 'update', id: Cost.where(:category => #cost.category, :option => #cost.option).first.id
else
respond_to do |format|
if #cost.save
format.html { redirect_to action: 'index', status: 303, notice: [true, 'Cost was successfully created.'] }
format.json { render json: #cost, status: :created, location: #cost }
else
format.html { render action: "new" }
format.json { render json: #cost.errors, status: :unprocessable_entity }
end
end
end
end
The problem is that it redirects me to, for example cost/9 url, which renders the show page. I want the id to send with the cost_params straight to the update method:
def update
#cost = Cost.find(params[:id])
respond_to do |format|
if #cost.update_attributes(cost_params)
format.html { redirect_to action: 'index', status: 303, notice: [true, 'Cost was successfully updated.'] }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #cost.errors, status: :unprocessable_entity }
end
end
end
Which should redirect to the index page.
is there any efficient way to do this?
And HTTP redirect always results in a GET request, not a POST request, so redirecting to update doesn't really make a lot of sense. That's not a Rails issue, that's just how HTTP works.
If you want to automatically update the relevant record, you have to do that from within the create action. The straightforward but lazy way would be to copy the code from update and paste it into if branch within create. The more correct way would be to extract the relevant part of update out into a separate, private method, and call that method from both create and update, something like:
def create
#cost = Cost.new(cost_params)
if Cost.exists?(:category => #cost.category, :option => #cost.option)
#cost = Cost.where(:category => #cost.category, :option => #cost.option).first
really_update
else
respond_to do |format|
if #cost.save
format.html { redirect_to action: 'index', status: 303, notice: [true, 'Cost was successfully created.'] }
format.json { render json: #cost, status: :created, location: #cost }
else
format.html { render action: "new" }
format.json { render json: #cost.errors, status: :unprocessable_entity }
end
end
end
end
def update
#cost = Cost.find(params[:id])
really_update
end
private def really_update
respond_to do |format|
if #cost.update_attributes(cost_params)
format.html { redirect_to action: 'index', status: 303, notice: [true, 'Cost was successfully updated.'] }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #cost.errors, status: :unprocessable_entity }
end
end
end
I am working through Agile Web Development with ruby on rails. While running a test, I get the following:
Error: LineItemsControllerTest#test_should_update_line_item:
NoMethodError: undefined method 'product_id' for nil:NilClass
test/controllers/line_items_controller_test.rb:13:in `block in <class:LineItemsControllerTest>
Here is my test file
require 'test_helper'
class LineItemsControllerTest < ActionController::TestCase
test "should create line_item" do
assert_difference('LineItem.count') do
post :create, product_id: products(:ruby).id
end
assert_redirected_to cart_path(assigns(:line_item).cart)
end
test "should update line_item" do
patch :update, id: #line_item, line_item: { product_id: #line_item.product_id }
assert_redirected_to line_item_path(assigns(:line_item))
end
end
Could someone kindly explain why I get a NoMethodError: undefined method while the book says it should be fine?
Thank you!
Update 1
As per Boltz0r's comment below, here are my create and update methods. I tried comparing what I have versus what is in the book and can't seem to find the problem.
def create
product = Product.find(params[:product_id])
#line_item = #cart.add_product(product.id)
respond_to do |format|
if #line_item.save
format.html { redirect_to #line_item.cart, notice: 'Line item was successfully created.' }
format.json { render :show, status: :created, location: #line_item }
else
format.html { render :new }
format.json { render json: #line_item.errors, status: :unprocessable_entity }
end
end
end
def update
respond_to do |format|
if #line_item.update(line_item_params)
format.html { redirect_to #line_item, notice: 'Line item was successfully updated.' }
format.json { render :show, status: :ok, location: #line_item }
else
format.html { render :edit }
format.json { render json: #line_item.errors, status: :unprocessable_entity }
end
end
end
I've checked the actual example from the book so will now post the answer so maybe somebody else could see it. If you going to test is update operation you should have something to update first. That's why the test from the book and any other real-life one has setup method to pre-create test item:
setup do
#line_item = line_items(:one)
end
What that error means is that in here:
patch :update, id: (at)line_item, line_item: { product_id: (at)line_item.product_id }
the (at)line_item is nil. so it means you probably have an error either on the create method or maybe in the update method (you are not getting the right line_item)
In a model I have:
before_destroy :ensure_not_referenced_by_any_shopping_cart_item
and
def ensure_not_referenced_by_any_shopping_cart_item
unless shopping_cart_items.empty?
errors.add(:base, "This item can't be deleted because it is present in a shopping cart")
false
end
end
When the item is present in a cart, it is not destroyed (which is good), and I see the error if I log it in the action..
def destroy
#product = Beverage.find(params[:id])
#product.destroy
logger.debug "--- error: #{#product.errors.inspect}"
respond_to do |format|
format.html { redirect_to beverages_url }
format.json { head :ok }
end
end
..but the instance variable on which the error message was set is abandoned when the redirect_to happens, so the user never sees it.
How should the error message be persisted to the next action so it can be shown in its view?
Thanks!
I would recommend using a flash message to relay the error information.
respond_to do |format|
format.html { redirect_to beverages_url, :alert => "An Error Occurred! #{#products.errors[:base].to_s}"
format.json { head :ok }
end
Something to that effect. That is how I have handled similar issues in my own apps, but it depends on the detail of information you want to display to the user.
You need to put the error in the flash. Something roughly like
def destroy
#product = Beverage.find(params[:id])
if #product.destroy
message = "Product destroyed successfully"
else
message = "Product could not be destroyed"
end
respond_to do |format|
format.html { redirect_to beverages_url, :notice => message }
format.json { head :ok }
end
end
Note that you also need to be printing out messages in your application.html.erb file.
You can do it with two messages, one with the status OK, and another with NO OK (unprocessable_entity for example, here's more).
def destroy
#product = Beverage.find(params[:id])
respond_to do |format|
if #product.destroy
format.html { redirect_to beverages_url, notice: "Product destroyed successfully", status: :ok}
format.json { head :ok, status: :ok}
else
format.html { redirect_to beverages_url, alert: "Product could not be destroyed", status: :unprocessable_entity}
format.json {head :no_content, status: :unprocessable_entity }
end
end
end
In Rails 4, you can do like that
def destroy
#product = Beverage.find(params[:id])
respond_to do |format|
if #product.destroy
format.html { redirect_to products_url, notice: 'Product was successfully destroyed.' }
format.json { head :ok }
else
format.html { render :show }
format.json { render json: #product.errors, status: :unprocessable_entity }
end
end
end