rails controller test failing non-deterministicly wrt state leak (I think) - ruby-on-rails

I have a standard rest controller (with load_and_authorize_resource) for which I have the following strong params allowed:
def subscription_params
params.require(:subscription).permit(:email,:confirmed)
end
update action:
def update
respond_to do |format|
if #subscription.update(subscription_params)
format.html { redirect_to #subscription, notice: 'Subscription was successfully updated.' }
format.json { render :show, status: :ok, location: #subscription }
else
format.html { render :edit }
format.json { render json: #subscription.errors, status: :unprocessable_entity }
end
end
end
In my test I have:
test "registered can edit confirmed" do
u = users(:registered)
sign_in u
#subscription = u.subscription
new_value = !#subscription.confirmed
patch :update, id: #subscription, subscription: { confirmed: new_value, email: #subscription.email, token: #subscription.token }
assert_response :redirect
assert_equal new_value, u.subscription.confirmed
assert_redirected_to subscription_path(#subscription)
assert_includes flash[:notice], "Subscription was successfully updated."
end
which fails non-deterministically (the confirmed field isn't updated). I don't know enough about rails' ecosystem to know where the problem is. I'm using devise and cancancan.
If I remove config.active_support.test_order = :random the test fails every time. And If I run the tests that fail on their own they always pass. Which lead me to believe that state is leaking between tests and causing issues but I can't figure out what.

Turns out I needed to call #subscription.reload.

Related

RSpec: Assert successful create request without assigns method

I have this code in a create method inside a Rails controller:
if #product.save
format.html { redirect_to #product, notice: 'Product was successfully created.' }
To test this code, I have this expectation in an RSpec file:
expect(response).to redirect_to(assigns(:product))
Using assigns is deprecated/has been moved to a gem and frankly I don't care if #product or #my_product has been created in the controller. Actually I just want to know if I have been redirected to /products/<some-id>. Is there a (recommended) way to do so?
If you want render new you you'll need add gem 'rails-controller-testing' to your Gemfile.
After read your comments i guess your action #create is look like that:
def create
#product = Product.new(product_params)
respond_to do |format|
if #product.save
format.html { redirect_to #product, notice: 'Product was successfully created.' }
format.json { render :show, status: :created, location: #product }
else
format.html { render :new }
format.json { render json: #product.errors, status: :unprocessable_entity }
end
end
end
You could do a test like that:
describe 'POST /products' do
context 'when everithing is ok' do
it 'returns the product' do
post products_url, params: { product: { description: 'lorem ipsum', title: 'lorem ipsum' } }
expect(response).to redirect_to(product_url(Product.last))
end
end
context 'when something worong' do
it 'redirect to new' do
post products_url, params: { product: { description: 'lorem ipsum' } }
expect(response).to render_template(:new)
end
end
end
This GitHub Issue explains why assigns is deprecated
Testing what instance variables are set by your controller is a bad idea. That's grossly overstepping the boundaries of what the test should know about. You can test what cookies are set, what HTTP code is returned, how the view looks, or what mutations happened to the DB, but testing the innards of the controller is just not a good idea.
you can test the response status code using have_http_status matcher
expect(response).to have_http_status(:success)

how to show the user an error after raise ActiveRecord::Rollback in rails?

I am creating a user after I create a client, but if the user already exists I roll back the transaction, but how do I tell the user something happened with a notice?
So far I have
def create
ActiveRecord::Base.transaction do
#affiliate = Affiliate.new(affiliate_params)
respond_to do |format|
if #affiliate.save
if User.find_by_email(user_params[:email]).nil?
#user = User.create!(user_parameter)
format.html {redirect_to :back, notice: 'Thanks for your submission'}
format.json {render :show, status: :created, location: #affiliate}
else
raise ActiveRecord::Rollback
end
else
format.html {render :signup, layout: "sign-ups"}
format.json {render json: #affiliate.errors, status: :unprocessable_entity}
end
end
end
end
I tried using the render and redirect but none worked.
Generally speaking, both raising exceptions while encountering expected conditions and manual rollbacks are considered code-smells in a Rails application.
A more idomatic approach might involve checking the existence of the user before saving the affiliate in the controller, or moving the logic into a model validation.
To answer your specific question w/o re-engineering everything, you could simply check performed? since all other paths render.
def create
ActiveRecord::Base.transaction do
...
format.html {redirect_to :back, notice: 'Thanks for your submission'}
format.json {render :show, status: :created, location: #affiliate}
...
else
...
format.html {render :signup, layout: "sign-ups"}
format.json {render json: #affiliate.errors, status: :unprocessable_entity}
...
end
end
unless performed?
# render duplicate email notice, re-display form
end
end
That said, below is a more typical approach. There are still some subtle issues but they would require diving into the specifics of your application.
# Add #email unique validation to User model, if not already implemented.
# class User < ApplicationRecord
# ...
# before_validation :normalize_email # downcase, etc
# validates :email, unique: true # should unique constraint on DB also
# ...
# end
def create
success = nil
ActiveRecord::Base.transaction do
#affiliate = Affiliate.new(affiliate_params)
#affiliate.save
# I'm guessing there is a relationship between the Affiliate and
# the User which is not being captured here?
#user = User.new(user_params)
#user.save
# Validation errors will populate on both #affiliate and #user which can
# be presented on re-display (including duplicate User#email).
# Did both the #affiliate and #user validate and save?
success = #affiliate.persisted? && #user.persisted?
# If one or both failed validation then roll back
raise ActiveRecord::Rollback unless success
end
respond_to do |format|
if success
format.html { redirect_to :back, notice: 'Thanks for your submission' }
format.json { render :show, status: :created, location: #affiliate }
else
# Either #affiliate or #user didn't validate. Either way, errors should
# be presented on re-display of the form. Json response needs work.
format.html { render :signup, layout: "sign-ups" }
format.json { render json: #affiliate.errors, status: :unprocessable_entity }
end
end
end

No route matches in show action- missing required keys: [:id]

I looked at a few different answers similar to this question, but they all are getting the id passed into the params hash, it is just coming back as a nil value. I cant seem to get the id to be whitelisted and I cant figure out why.
This is my create and show action, also includes the params. I explicitly whitelisted id although I dont know if it is necessary.
def show
#board = Board.find(params[:id])
end
def create
#board = Board.new(board_params)
if #board.save
# flash[:notice] = 'You successfully created your post!'
redirect_to board_path
else
render 'new'
# flash[:danger] = 'One or more errors occurred when processing your post'
end
end
private
def board_params
# params.require(:board).permit!
params.require(:board).permit(:id, :project_name, :project_description, :postition_title, :project_url, :project_headquarters, :application_process)
end
The output of my routes show action
board GET /boards/:id(.:format) boards#show
And the output of the params hash on board#create
Parameters: {"utf8"=>"✓", "authenticity_token"=>"k+KKRhBk4Dnba1vxtFCanI2grfhNXFSgJpfEBLnhPdablfOsXRi1wBehPpM1qgx1pySrqxVHTeiwkneluXwRIQ==", "board"=>{"project_name"=>"sdf", "postition_title"=>"sdf", "project_headquarters"=>"sdf", "project_url"=>"fsd", "project_description"=>"dfs", "application_process"=>"dfs"}, "commit"=>"Create Board"}
Can anyone see what I need to change in order to get the id passed in correctly?
Try this.
Take :id out of params.
def show
end
def create
#board = Board.new(board_params)
respond_to do |format|
if #board.save
format.html { redirect_to #board, notice: 'Board was successfully created.' }
format.json { render :show, status: :created, location: #board }
else
format.html { render :new }
format.json { render json: #board.errors, status: :unprocessable_entity }
end
end
end
in create action...
change ..
redirect_to board_path
to...
redirect_to board_path(#board)
###OR
### this calls the show action
redirect_to #board

Rails 5 Failing Test ActiveRecord::AssociationTypeMismatch

I'm currently working through Agile Development with Rails 5 and have run into an issue with my testing. Namely, there is an association mismatch going on where it expects the Product but gets a String. I've tried fixing the line in my line_items_controller_test.rb from #line_item.product to #line_item.product_id to no avail and have poked around as much as I know how.
Here is my test file line_items_controller_test.rb
test "should update line_item" do
patch line_item_url(#line_item), params: { line_item: { cart_id: #line_item.cart_id, product: #line_item.product } }
assert_redirected_to line_item_url(#line_item)
end
and my test failure from the terminal
Error:
LineItemsControllerTest#test_should_update_line_item:
ActiveRecord::AssociationTypeMismatch: Product(#70143083725900) expected, got String(#70143078473840)
app/controllers/line_items_controller.rb:47:in `block in update'
app/controllers/line_items_controller.rb:46:in `update'
test/controllers/line_items_controller_test.rb:40:in `block in <class:LineItemsControllerTest>'
bin/rails test test/controllers/line_items_controller_test.rb:39
and here is the controller itself where it mentions 47 and 46 beginning with respond_to
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
Let me know if there's any more info you need. You can also check my repo at https://github.com/jamesemcc/depot
Ok. There are problems not only in your test, but in app.
https://github.com/jamesemcc/depot/blob/master/app/controllers/line_items_controller.rb#L75 You should permit product_id, not product here. You cannot pass Product object from browser.
You should pass product_id from view and from test
Good Luck!

Send Email Notice in Rails App Upon Comment Creation

I am using the gem 'foreigner' and setup comment for my app and everything works. However, I would also like to notify my users whenever a comment is created. I've got two users, customers and developers. Customers can post comments and Developers can post comments.
How would I setup my comments_controller.rb file to figure out if its a customer or developer posting the comment and then send email with the right template.
So far I've tried the following with no work;
def create
#comment = Comment.new(params[:comment])
respond_to do |format|
if #comment.save
if current_user.is_developer?
Notifier.developer_notify(#developer).deliver
elsif current_user.is_customer?
Notifier.customer_notify(#customer).deliver
end
format.html { redirect_to :back, notice: 'Comment was successfully created.' }
# format.json { render json: #comment, status: :created, location: #comment }
else
format.html { render action: "new" }
# format.json { render json: #comment.errors, status: :unprocessable_entity }
end
end
end
"developer_notify" and "customer_notify" being the class defined in my Notifier mailer.
My "Notifier" mailer looks like this so far;
def developer_notify(joblisting)
#joblisting = joblisting
mail(:to => #joblisting.current_user.email, :subject => "There's a new comment.")
end
#Joblisting is the job that is referenced to as each Joblisting has its own comments from customers and developers.
Doing the above, gives me an error - undefined method 'current_user' for nil:NilClass
So I'm guessing it's not finding the Joblisting ID nor is it finding the customers email address, then if the customer posted a comment for that same job, it would send email to the developer with notification of a new comment posted.
Any suggestions?
you have passing joblisting from your controller:
def create
#comment = Comment.new(params[:comment])
#you have define here joblisting for example:
joblisting = JobListing.first #adapt the query to your needs
respond_to do |format|
if #comment.save
if current_user.is_developer?
#here add joblisting as argument after #developer
Notifier.developer_notify(#developer, joblisting).deliver
elsif current_user.is_customer?
#here add joblisting as argument after #developer
Notifier.customer_notify(#customerm, joblisting).deliver
end
format.html { redirect_to :back, notice: 'Comment was successfully created.' }
# format.json { render json: #comment, status: :created, location: #comment }
else
format.html { render action: "new" }
# format.json { render json: #comment.errors, status: :unprocessable_entity }
end
end
end
on notifier mailer
def developer_notify(#developer, joblisting)
#joblisting = joblisting
mail(:to => #joblisting.current_user.email, :subject => "There's a new comment.")
end
Regards!

Resources