Rails 4: Factory Girl & Rspec with associated Model - ruby-on-rails

I previously fixed an issue with some code that works though it is a little ugly. Problem now is that it breaks my tests! The idea here is that I can create a Campaign and associate 1 zip-file and one-to-many pdfs.
Previous question and solution:
Rails 4.2: Unknown Attribute or Server Error in Log
Here is the failure message:
console
1) CampaignsController POST #create with valid params
Failure/Error: post :create, campaign: attributes_for(:campaign)
ActiveRecord::RecordNotFound:
Couldn't find Uploadzip with 'id'=
# ./app/controllers/campaigns_controller.rb:15:in `create'
# ./spec/controllers/campaigns_controller_spec.rb:36:in `block (4 levels) in <top (required)>'
..and the rest of the code.
spec/factories/campaigns.rb
FactoryGirl.define do
factory :campaign do |x|
x.sequence(:name) { |y| "Rockfest 201#{y} Orange County" }
x.sequence(:comment) { |y| "Total attendance is #{y}" }
end
end
spec/controllers/campaigns_controller.rb
describe "POST #create" do
context "with valid params" do
before(:each) do
post :create, campaign: attributes_for(:campaign)
end
.........
end
app/controllers/campaigns_controller.rb
class CampaignsController < ApplicationController
......................
def create
#campaign = Campaign.new(campaign_params)
if #campaign.save
zip = Uploadzip.find(params[:uploadzip_id])
zip.campaign = #campaign
zip.save
flash[:success] = "Campaign Successfully Launched!"
redirect_to #campaign
else
................
end
end
.......................
private
def campaign_params
params.require(:campaign).permit(:name, :comment, :campaign_id, uploadpdf_ids: [])
end
end
This appears simple and I assume it is, yet I've tried quit a few things and can't seem to get it to pass. How would I support the new controller logic in this test? Any help is appreciated.
UPDATE
With zetitic's advice, I created the following code in which successfully passes.
before(:each) do
#uploadzip = create(:uploadzip)
post :create, campaign: attributes_for(:campaign), uploadzip_id: #uploadzip
end

Add the uploadedzip_id to the posted params:
before(:each) do
post :create, campaign: attributes_for(:campaign), uploadedzip_id: 123456
end

Related

expected result to have changed by 1, but was changed by 0

I'm new to RSpec and this error is all new to me. Everything seems routine so I can't seem to debug this issue myself. ERROR: expected result to have changed by 1, but was changed by 0. I'll post my code for clarity.
SUBSCRIBER FACTORY:
FactoryGirl.define do
factory :subscriber do
first_name "Tyler"
last_name "Durden"
email "tyler#example.com"
phone_number "8765555"
end
end
CONTROLLER:
class CommentsController < ApplicationController
def new
#comment = Comment.new
end
def create
#subscriber = Subscriber.order('updated_at desc').first
#comment = #subscriber.comments.build(comments_params)
if #comment.save
flash[:notice] = "Thank you!"
redirect_to subscribers_search_path(:comments)
else
render "new"
end
end
private
def comments_params
params.require(:comment).permit(:fav_drink, :subscriber_id)
end
end
SPEC:
require "rails_helper"
describe SubscribersController do
include Devise::TestHelpers
let(:user) { FactoryGirl.create(:user) }
let(:subscriber) { FactoryGirl.attributes_for(:subscriber) }
it "creates a new comment" do
sign_in(user)
comment = FactoryGirl.attributes_for(:comment)
expect { post :create, subscriber: subscriber, comment: comment }.to change{ Comment.count }.by(1)
end
end
ERROR:
Failure/Error: expect { post :create, subscriber: subscriber, comment: comment }.to change{ Comment.count }.by(1)
expected result to have changed by 1, but was changed by 0
# ./spec/controllers/comment_spec.rb:13:in `block (2 levels) in <top (required)>'
Here, you're showing your comments controller, expecting one of its actions to be hit. However, your test case is actually calling the create route of the Subscriptions controller.
When, in your test case, you write describe SubscribersController do, you are establishing a scope for the HTTP requests you make in that block.
So when you call post :create, subscriber: subscriber, comment: comment,
it's the Subscriptions controller which is being hit.
In general, in order to debug, you should
check that the area of code in question is being called
check that values are correct (here, that would mean that the Comment.create object is successfully saved.

Create a post controller spec test

I am doing a controller test but it seems that spec.rb is wrong.
Do you have a suggestion ?
This is my posts_controller.rb:
class PostsController < ApplicationController
def create
#post = Post.new(post_params)
if #post.save
redirect_to #wall
end
end
def destroy
#post.destroy
end
private
def post_params
params.require(:post).permit(:wall, :content)
end
end
and this is my posts_controller_spec.rb:
require 'rails_helper'
describe PostsController do
let(:wall) { create(:wall) }
describe "#create" do
it "saves the new post in the wall" do
post :create, { wall_id: wall, content: "Some text I would like to put in my post" }
end
end
describe "#destroy" do
it "deletes the post in the wall" do
end
end
end
could you please help me to correct my spec.rb?
this is my error:
PostsController
#create
saves the new post in the wall (FAILED - 1)
#destroy
deletes the post in the wall
Failures:
1) PostsController#create saves the new post in the wall
Failure/Error: post :create, post: { wall: wall, content: "Some text I would like to put in my post" }
ActiveRecord::AssociationTypeMismatch:
Wall(#2159949860) expected, got String(#2155957040)
# ./app/controllers/posts_controller.rb:3:in create'
# ./spec/controllers/posts_controller_spec.rb:8:inblock (3 levels) in '
# -e:1:in `'
Finished in 0.9743 seconds (files took 3.94 seconds to load)
2 examples, 1 failure
Failed examples:
rspec ./spec/controllers/posts_controller_spec.rb:7 # PostsController#create saves the new post in the wall
Thank you in advance
Your spec doesn't include any expectations, so it's "wrong" in that sense. I suggest you google "RSpec expectations" and/or read the docs (i.e. https://relishapp.com/rspec/rspec-expectations/docs).
As for the error you mentioned in your comment, that reflects a problem with your production code (i.e. the lack of a redirect or render or some create template in the case the #post.save returns nil). Again, googling the error should yield information to help you address this problem or your can read http://guides.rubyonrails.org/layouts_and_rendering.html. If you're new to Rails entirely, I suggest following one of the tutorials, such as https://www.railstutorial.org/
You should also update your question to include that error information, since it's highly relevant and the question is essentially incomplete without it.
You should expect something on your tests. For example, you could do like this:
RSpec.describe PostsController, type: :controller do
let!(:wall) { create(:wall) }
let(:test_post) {
create(:post, wall_id: wall.id, content: "Something") }
}
describe "POST #create" do
let(:post) { assigns(:post) }
let(:test_wall) { create(:wall) }
context "when valid" do
before(:each) do
post :create, params: {
post: attributes_for(:post, wall_id: test_wall.id, content: "Anything")
}
end
it "should save the post" do
expect(post).to be_persisted
end
end
end
end
This way you are expecting a response from rails when you post you parameters. I just coded the post part of the test.

Rspec controller test for callback after_save

I am trying to test to see if posting to a create method in my controller triggers a callback I defined with after_save
Here's the controller method being posted to
def create
#guest = Guest.new(guest_params)
#hotel = Hotel.find(visit_params[:hotel_id])
#set visit local times to UTC
#visit= Visit.new(visit_params)
#visit.checked_out_at = (DateTime.now.utc + visit_params[:checked_out_at].to_i.to_i.days).change(hour: #visit.hotel.checkout_time.hour)
#visit.checked_in_at = Time.now.utc
##visit.user_id = current_user.id
#self_serve = (params[:self_serve] && params[:self_serve] == "true")
if #guest.save
#visit.guest_id = #guest.id
if #visit.save
if #self_serve
flash[:notice] = "#{#visit.guest.name}, you have successfully checked in!."
redirect_to guest_checkin_hotel_path(#visit.hotel)
else
flash[:notice] = "You have successfully checked in #{#visit.guest.name}."
redirect_to hotel_path(#visit.hotel)
end
else
render "new"
end
else
render "new"
end
end
Here's my spec/controllers/guests_controller_spec.rb test that is failing
RSpec.describe GuestsController, :type => :controller do
describe "#create" do
let!(:params) do { name: "John Smith", mobile_number: "9095551234" } end
context "when new guest is saved" do
it "triggers create_check_in_messages callback" do
post :create, params
expect(response).to receive(:create_check_in_messages)
end
end
end
end
Here is my models/concerns/visit_message.rb callback file
module VisitMessage
extend ActiveSupport::Concern
included do
after_save :create_check_in_messages
end
def create_check_in_messages
. . .
end
end
Here is the fail message when I run 'rspec spec/controllers/guests_controller_spec.rb'
1) GuestsController#create when new guest is saved triggers create_check_in_messages callback
Failure/Error: post :create, params
ActionController::ParameterMissing:
param is missing or the value is empty: guest
# ./app/controllers/guests_controller.rb:63:in `guest_params'
# ./app/controllers/guests_controller.rb:10:in `create'
# ./spec/controllers/guests_controller_spec.rb:36:in `block (4 levels) in <top (required)>'
I've been searching all over stackoverflow with no luck. I appreciate any help!
I am assuming that the guest_params method in the controller looks something like this:
def guest_params
params.require(:guest).permit(....)
end
If that is the case, you need to update the POST call in your test case thusly:
post :create, {guest: params}
On a side note, your controller is unnecessarily bloated. I would read up on working with associated models to streamline your code, specifically, using accepts_nested_attributes_for:
http://guides.rubyonrails.org/association_basics.html#detailed-association-reference
http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html

Count; find User with id= Minitest

I am following Michael Hartl's Ruby on Rails tutorial and I am not sure why I am getting this Error when according to the tutorial everything should pass:
1) Error:
UsersControllerTest#test_should_get_show:
ActiveRecord::RecordNotFound: Couldn't find User with 'id'=
app/controllers/users_controller.rb:7:in `show'
test/controllers/users_controller_test.rb:10:in `block in <class:UsersControllerTest>'
My minitest:
require 'test_helper'
class UsersSignupTest < ActionDispatch::IntegrationTest
# add invalid information and test that the User.count never changes
# also test that the sign up path is visited after invalid sign up
test "invalid signup information" do
# visit the signup path using get
get signup_path
assert_no_difference "User.count" do
post users_path, user: { name: "", email: "user#invalid", password: "foo", password_confirmation: "bar"}
end
assert_template "users/new"
end
end
I compared my users_controller to the official github tutorial and it looks the same
Users controller:
class UsersController < ApplicationController
def new
#user = User.new
end
def show
#user = User.find(params[:id])
end
def create
# strong parameters
#user = User.new(user_params)
if #user.save
# handle save
else
render 'new'
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password, :password_confirmation)
end
end
I dont really understand why id is being searched for as well. My database is empty with no users. I am currently testing that inputing invalid parameters for sign up will not add another user.
my UserControllerTest:
require 'test_helper'
class UsersControllerTest < ActionController::TestCase
test "should get new" do
get :new
assert_response :success
end
test "should get show" do
get :show
assert_response :success
end
end
Show renders a page for specific user, so you need to pass it the id param. Change the test to:
test "should get show" do
user = User.create
get :show, id: user.id
assert_response :success
end
FYI, A small breakdown of the error message:
1) Error:
Error
UsersControllerTest#test_should_get_show:
In test test_should_get_show in class UserControllerTest
ActiveRecord::RecordNotFound: Couldn't find User with 'id'=
Database doesn't contain User object with empty id
app/controllers/users_controller.rb:7:in `show'
File and line that directly caused the error
test/controllers/users_controller_test.rb:10:in `block in <class:UsersControllerTest>'
File and line where the action originated from.

rspec controller testing, Expected response to be a <redirect>, but was <200>

I'm using rspec and Factory Girl for testing. When testing the POST #create section of my posts_controller I'm getting the error in the title.
Failures:
1) PostsController POST #create with valid attributes redirects to the post
Failure/Error: response.should redirect_to Post.last
Expected response to be a <redirect>, but was <200>
# ./spec/controllers/posts_controller_spec.rb:59:in `block (4 levels) in <top (required)>'
This is the code from the spec being tested. I'm sure it's not the most efficient way of doing this, but it does work.
def create
#post = Post.new(
:text => post_params[:text],
:embed => post_params[:embed],
:user => current_user,
:title => post_params[:title],
:tag_list => post_params[:tag_list],
:cagegory_ids => post_params[:category_ids]
)
if #post.save
redirect_to #post
else
render 'new'
end
end
...
private
def post_params
params.require(:post).permit(:title, :text, :embed, :user_id, :tag_list,
:category_ids => [])
end
Here's the factory.
FactoryGirl.define do
factory :post do
title { Faker::Lorem.characters(char_count = 20) }
text { Faker::Lorem.characters(char_count = 150) }
user
categories {
Array(5..10).sample.times.map do
FactoryGirl.create(:category)
end
}
end
end
And the relevant part of the spec
describe "POST #create" do
context "with valid attributes" do
it "saves the new post" do
expect{
post :create, post: FactoryGirl.create(:post).attributes
}.to change(Post,:count).by(1)
end
it "redirects to the post" do
post :create, post: FactoryGirl.create(:post).attributes
response.should redirect_to Post.last
end
end
end
The other test, "saves the new post," works fine. I've tried other variations of the redirect_to line, such as "redirect_to(posts_path(assigns[:post])) and it throws the same error.
Any ideas?
OK, I fixed my problem. It isn't pretty but it works.
The issue is with Factory Girl and associations, and I'm definitely not the first to have that problem, but none of the other solutions worked.
I ended up adding this before :each at the top of the POST #create section...
describe "POST #create" do
before :each do
Post.destroy_all
#cat = FactoryGirl.create(:category)
#newpost = FactoryGirl.build(:post)
#post_params = {category_ids: [#cat.id]}.merge(#newpost.attributes)
end
...
...to put the post params into a new hash that I could call later on in the code like this...
it "saves the new post" do
expect{
post :create, post: #post_params
}.to change(Post,:count).by(1)
end
it "redirects to the post" do
post :create, post: #post_params
response.should redirect_to Post.last
end
So this is solved. It adds a bit of overhead to the test but it works. I won't mark this as THE solution for a couple of days in case someone else comes around with some better, simpler code. I definitely welcome any more ideas.
I assume the problem is in factory. Most likely post instance didn't pass validations and controller renders new view, which is not redirect, but success 200. Add logger in the controller and take a look if record actually saves. You can also read through test log tail -f log/test.log.

Resources