I'm trying to write a controller test for my update route using Rspec with factory girl but I can't get my test to pass validations even though I'm pretty sure the data in my factories is valid.
Here are my factories:
FactoryGirl.define do
factory :user do
username { Faker::Internet.user_name(8) }
password 'password'
end
factory :post do
title { Faker::Lorem.sentence }
body { Faker::Lorem.paragraph }
author { Faker::Internet.user_name(8) }
end
end
here is my model:
class Post < ActiveRecord::Base
validates :title, :body, :author, presence: true
end
Here is my test:
require 'rails_helper'
describe PostsController do
let!(:user) { FactoryGirl.create :user }
let!(:post) { FactoryGirl.create :post }
let(:attributes) { FactoryGirl.attributes_for :post }
describe 'PUT #update' do
let(:title) { "A treatise on Malomars." }
it 'updates a field on a blog post' do
put :update, id: post.id, post: {title: title}
expect(post.reload.title).to eq(post.title)
end
end
end
The error I'm getting is:
Failure/Error: put :update, id: post.id, post: {title: title}
ActiveRecord::RecordInvalid:
Validation failed: Body can't be blank
EDIT---
here is the controller:
class PostsController < ApplicationController
def index
#posts = Post.all
end
def new
end
def create
post = Post.new
post.title = params[:title]
post.body = params[:body]
post.author = "#{session[:username].titleize} Force"
redirect_to root_path
post.save!
end
def show
p session[:id]
#post = Post.find(params[:id])
end
def update
post = Post.find(params[:id])
post.title = params[:post][:title]
post.body = params[:post][:body]
post.save!
redirect_to root_path
end
def destroy
post = Post.find(params[:id])
post.destroy
redirect_to root_path
end
end
Strong parameters aside, you're setting post.body to nil since you're not passing a value for the body parameter in your test. When you call save! in your controller, you're getting an error because you have a validation for body being present (i.e. not nil).
Related
This question's been solved. Check the bottom line.
Nowadays, I'm building my movie review website on Ruby on Rails but stuck with the Comment.count error. Already checked and tried a lot of options, but any solutions didn't work. Actually, my website works well, but still this test says the error, and I don't understand what is the problem and why it happens. Could you help me to fix this error? Thank you.
error code
test_comment_interface#CommentsInterfaceTest (3.43s)
"Comment.count" didn't change by 1.
Expected: 39
Actual: 38
test/integration/comments_interface_test.rb:19:in `block in <class:CommentsInterfaceTest>'
comments_interface_test
require 'test_helper'
class CommentsInterfaceTest < ActionDispatch::IntegrationTest
def setup
#user = users(:michael)
end
test "comment interface" do
log_in_as(#user)
get root_path
assert_select 'div.pagination'
assert_no_difference 'Comment.count' do
post comments_path, params: { comment: { content: "" } }
end
assert_select 'div#error_explanation'
content = "This comment really ties the room together"
assert_difference 'Comment.count', 1 do #<-----------------------error point
post comments_path, params: { comment: { content: content } }
end
assert_redirected_to root_url
follow_redirect!
assert_match content, response.body
assert_select 'a', text: 'delete'
first_comment = #user.comments.paginate(page: 1).first
assert_difference 'Comment.count', -1 do
delete comment_path(first_comment)
end
get user_path(users(:archer))
assert_select 'a', text: 'delete', count: 0
end
end
comment.rb
class Comment < ApplicationRecord
belongs_to :user
belongs_to :movie, optional: true, primary_key: "id"
has_many :favorites, dependent: :destroy
default_scope -> { order(created_at: :desc) }
validates :user_id, presence: true
validates :movie_id, presence: true
validates :content, presence: true, length: { maximum: 250}
end
comments_controller
class CommentsController < ApplicationController
include HTTParty
before_action :logged_in_user, only: [:create, :destroy]
before_action :correct_user, only: :destroy
def create
#comment = current_user.comments.build(comment_params)
comment_count = Comment.where(movie_id: params[:id]).where(user_id: current_user.id).count
if #comment.valid?
if comment_count < 1
#comment.save
flash[:success] = "Comment created!"
redirect_to root_url
else
flash[:success] = "Can't post a comment twice on the same movie"
redirect_to request.referrer || root_url
end
else
#feed_items = []
render 'static_pages/home'
end
end
def destroy
#comment.destroy
flash[:success] = "Comment deleted"
redirect_to request.referrer || root_url
end
def edit
#movie_info = Movie.details(params[:movie_id])
#comment = Comment.find(params[:id])
if Movie.exists?(#movie_info["id"])
#movie_db = Movie.find(#movie_info["id"])
#comments = #movie_db.comments.paginate(page: params[:page])
end
end
def update
#comment = Comment.find(params[:id])
if #comment.update(comment_params)
redirect_to root_url
else
render :edit
end
end
private
def comment_params
params.require(:comment).permit(:content)
end
def correct_user
#comment = current_user.comments.find_by(id: params[:id])
redirect_to root_url if #comment.nil?
end
end
comments_controller_test
require 'test_helper'
class CommentsControllerTest < ActionDispatch::IntegrationTest
def setup
#user = users(:michael)
#comment = comments(:orange)
end
test "should redirect create when not logged in" do
assert_no_difference 'Comment.count' do
post comments_path, params: { comment: { content: "Lorem ipsum" } }
end
assert_redirected_to login_url
end
test "should redirect destroy when not logged in" do
assert_no_difference 'Comment.count' do
delete comment_path(#comment)
end
assert_redirected_to login_url
end
test "should redirect destroy for wrong comment" do
log_in_as(users(:michael))
comment = comments(:ants)
assert_no_difference 'Comment.count' do
delete comment_path(comment)
end
assert_redirected_to root_url
end
end
comments.yml
orange:
content: "I just ate an orange!"
created_at: <%= 10.minutes.ago %>
user: michael
movie: harry
tau_manifesto:
content: "Check out the #tauday site by #mhartl: http://tauday.com"
created_at: <%= 3.years.ago %>
user: michael
movie: harry
cat_video:
content: "Sad cats are sad: http://youtu.be/PKffm2uI4dk"
created_at: <%= 2.hours.ago %>
user: michael
movie: harry
most_recent:
content: "Writing a short test"
created_at: <%= Time.zone.now.to_s(:db) %>
user: michael
movie: harry
<% 30.times do |n| %>
comment_<%= n %>:
content: <%= Faker::Lorem.sentence(5) %>
created_at: <%= 42.days.ago %>
user: michael
movie: harry
<% end %>
ants:
content: "Oh, is that what you want? Because that's how you get ants!"
created_at: <%= 2.years.ago %>
user: archer
movie: harry
zone:
content: "Danger zone!"
created_at: <%= 3.days.ago %>
user: archer
movie: harry
tone:
content: "I'm sorry. Your words made sense, but your sarcastic tone did not."
created_at: <%= 10.minutes.ago %>
user: lana
movie: harry
van:
content: "Dude, this van's, like, rolling probable cause."
created_at: <%= 4.hours.ago %>
user: lana
movie: harry
From the comments below, I fixed some codes like this. Finally it worked, and the problem was solved.
comments_interface_test
def setup
#user = users(:michael)
#movie = movies(:harry) #add
end
assert_difference 'Comment.count', 1 do
post comments_path, params: { comment: { content: content, movie_id: #movie.id } } #fixed
end
comments_controller
private
def comment_params
params.require(:comment).permit(:content, :movie_id) #fixed
end
By looking at your Comment model it looks like a validation error. You have the following validations on the model but on the test, you didn't specify movie_id.
comment.rb
validates :user_id, presence: true
validates :movie_id, presence: true
Instead it should be something like the following params: { id: movie_id, comment: { content: content } }.
I'm new to rails and working on embedding associations into my models for my API. However adding the embeds has caused my specs to fire the following error:
Rails: 4.2.3 Ruby:2.2.1 Rspec: 3.3.2 FactoryGirl: 4.5.0
1) Api::V1::ProductsController GET #show returns the information about a reporter on a hash
Failure/Error: get :show, id: #product.id
SystemStackError:
stack level too deep
I think from looking at other answers on stack overflow that there is a problem with how I'm using my factories in my tests.
Here are the models:
product.rb
class User < ActiveRecord::Base
validates :auth_token, uniqueness: true
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
before_create :generate_authentication_token!
has_many :products, dependent: :destroy
def generate_authentication_token!
begin
self.auth_token = Devise.friendly_token
end while self.class.exists?(auth_token: auth_token)
end
end
user.rb
class Product < ActiveRecord::Base
validates :title, :user_id, presence: true
validates :price, numericality: { greater_than_or_equal_to: 0 },
presence: true
belongs_to :user
end
and the factories:
users.rb
FactoryGirl.define do
factory :user do
email { FFaker::Internet.email }
password "12345678"
password_confirmation "12345678"
end
end
products.rb
FactoryGirl.define do
factory :product do
title { FFaker::Product.product_name }
price { rand() * 100 }
published false
user
end
end
and here is the spec I'm getting all of the errors from.
products_controller_spec.rb
require 'spec_helper'
describe Api::V1::ProductsController do
describe "GET #show" do
before(:each) do
#product = FactoryGirl.create :product
get :show, id: #product.id
end
it "returns the information about a reporter on a hash" do
product_response = json_response[:product]
expect(product_response[:user][:email]).to eql #product.user.email
end
it "has the user as an embeded object" do
product_response = json_response[:product]
expect(product_response[:user][:email]).to eql #product.user.email
end
it { should respond_with 200 }
end
describe "GET #index" do
before(:each) do
4.times { FactoryGirl.create :product }
get :index
end
it "returns 4 records from the database" do
products_response = json_response
expect(products_response[:products]).to have(4).items
end
it "returns the user object into each product" do
products_response = json_response[:products]
products_response.each do |product_response|
expect(product_response[:user]).to be_present
end
end
it { should respond_with 200 }
end
describe "POST #create" do
context "when is succesfully created" do
before(:each) do
user = FactoryGirl.create :user
#product_attributes = FactoryGirl.attributes_for :product
api_authorization_header user.auth_token
post :create, { user_id: user.id, product: #product_attributes }
end
it "renders the json representation for the product record just created" do
product_response = json_response[:product]
expect(product_response[:title]).to eql #product_attributes[:title]
end
it { should respond_with 201 }
end
context "when is not created" do
before(:each) do
user = FactoryGirl.create :user
#invalid_product_attributes = { title: "Smart TV", price: "Twelve dolalrs" }
api_authorization_header user.auth_token
post :create, { user_id: user.id, product: #invalid_product_attributes }
end
it "renders an errors json" do
product_response = json_response
expect(product_response).to have_key(:errors)
end
it "renders the json errors on why the product could not be created" do
product_response = json_response
expect(product_response[:errors][:price]).to include "is not a number"
end
it { should respond_with 422 }
end
end
describe "PUT/PATCH #update" do
before(:each) do
#user = FactoryGirl.create :user
#product = FactoryGirl.create :product, user: #user
api_authorization_header #user.auth_token
end
context "when is successfully updated" do
before(:each) do
patch :update, { user_id: #user.id, id: #product.id,
product: { title: "An expensive TV"} }
end
it "renders the json representation for the updated user" do
product_response = json_response[:product]
expect(product_response[:title]).to eql "An expensive TV"
end
it { should respond_with 200 }
end
context "when is not updated" do
before(:each) do
patch :update, { user_id: #user.id, id: #product.id,
product: { price: "two hundred" } }
end
it "renders an errors json" do
product_response = json_response
expect(product_response).to have_key(:errors)
end
it "renders the json errors on why the user could not be created" do
product_response = json_response
expect(product_response[:errors][:price]).to include "is not a number"
end
it { should respond_with 422 }
end
end
describe "DELETE #destroy" do
before(:each) do
#user = FactoryGirl.create :user
#product = FactoryGirl.create :product, user: #user
api_authorization_header #user.auth_token
delete :destroy, { user_id: #user.id, id: #product.id }
end
it { should respond_with 204 }
end
end
And the Products Controller:
class Api::V1::ProductsController < ApplicationController
before_action :authenticate_with_token!, only: [:create, :update]
respond_to :json
def show
respond_with Product.find(params[:id])
end
def index
respond_with Product.all
end
def create
product = current_user.products.build(product_params)
if product.save
render json: product, status: 201, location: [:api, product]
else
render json: { errors: product.errors }, status: 422
end
end
def update
product = current_user.products.find(params[:id])
if product.update(product_params)
render json: product, status: 200, location: [:api, product]
else
render json: { errors: product.errors }, status: 422
end
end
def destroy
product = current_user.products.find(params[:id])
product.destroy
head 204
end
private
def product_params
params.require(:product).permit(:title, :price, :published)
end
end
user_serializer.rb
class UserSerializer < ActiveModel::Serializer
attributes :id, :email, :created_at, :updated_at, :auth_token
has_many :products
end
EDIT: Adds Products Controller
I test my destroy and update methods in hotel_controller and Im keep getting ActiveRecord:RecordNotFound error. Heres a screenshot
I think this is coz FactoryGirs doesnt save recods to the db. Help me pls to get things right.
hotels_controller.rb
class HotelsController < ApplicationController
before_action :signed_in_user, except: [:index, :show, :top5hotels]
...
def destroy
#hotel = current_user.hotels.find(params[:id])
#hotel.destroy
redirect_to hotels_url
end
def update
#hotel = current_user.hotels.find(params[:id])
if #hotel.update_attributes!(params[:hotel])
redirect_to #hotel, notice: "Hotel was successfully updated."
else
render "edit"
end
end
...
end
factories.rb
FactoryGirl.define do
factory :hotel do
name 'NewHotel'
star_rating 5
breakfast false
room_description 'Room Description'
price_for_room 500
user { create(:user) }
address { create(:address) }
end
factory :user do
sequence(:email) { |n| "user_mail.#{n}#gmail.com" }
name 'Yuri Gagarin'
password 'foobar'
password_confirmation 'foobar'
end
factory :rating do
value 5
user { create(:user) }
hotel { create(:hotel) }
end
factory :comment do
body "Heresanytextyouwant"
user { create(:user) }
hotel { create(:hotel) }
end
factory :address do
country 'Country'
state 'State'
city 'City'
street 'Street'
end
end
hotels_controller_spec.rb
require 'spec_helper'
describe HotelsController do
before { sign_in user, no_capybara: true }
...
describe "destroy action" do
it "redirects to index action when hotel is destroyed" do
hotel = create(:hotel)
delete :destroy, id: hotel.id
expect(response).to redirect_to(hotels_url)
end
end
describe "update action" do
it "redirects to the hotel" do
hotel = create(:hotel)
put :update, id: hotel.id, hotel: FactoryGirl.attributes_for(:hotel)
expect(assigns(:hotel)).to be_eq(hotel)
#expect(response).to render_template('show')
end
end
end
FactoryGirl IS saving records to db.
The trouble is the current_user is not the same user that the hotel belongs to, so when you try to retrieve the hotel record it's not found.
Try changing...
#hotel = current_user.hotels.find(params[:id])
to...
#hotel = Hotel.find(params[:id])
And you'll see it works.
If you want to keep the original code, then in the test you should be doing...
hotel = create(:hotel, user: current_user)
I want to test comments controller, action create, but I don't know how do this.
comments_controller
class CommentsController < ApplicationController
def create
#hotel = Hotel.find(params[:hotel_id])
#comment = #hotel.comments.new(comment_params)
#comment.user_id = current_user.id
#comment.save
redirect_to #hotel
end
private
def comment_params
params.require(:comment).permit(:user_id, :body, :hotel_id)
end
end
routes.rb
resources :hotels do
resources :comments
get 'list', on: :collection
post 'comment'
end
comments_controller_spec.rb
require 'rails_helper'
describe CommentsController do
login_user
describe 'POST create' do
it 'create a new comment with valid attributes' do
expect{
comment_attr = attributes_for(:comment)
post :create, comment: comment_attr
}.to change(Comment,:count).by(1)
end
it 'redirects to the new comment' do
comment_attr = attributes_for(:comment)
post :create, comment: comment_attr
expect(response).to redirect_to hotels_path(hotel)
end
end
end
factories.rb
FactoryGirl.define do
factory :user do |user|
user.email { Faker::Internet.email }
user.password 'password'
user.password_confirmation 'password'
end
factory :hotel do |hotel|
hotel.title 'Hotel'
hotel.description 'This is a some description for hotel'
hotel.breakfast true
hotel.price 20500
hotel.address { create(:address) }
hotel.user { create(:user) }
hotel.avatar { fixture_file_upload(Rails.root + 'spec/fixtures/images/example.jpg', "image/jpg") }
end
factory :comment do |comment|
comment.body 'This is a some comment ^_^'
comment.user { create(:user) }
comment.hotel { create(:hotel) }
end
factory :address do |address|
address.country { Faker::Address.country }
address.state { Faker::Address.state }
address.city { Faker::Address.city }
address.street { Faker::Address.street_name }
end
end
But I have this error:
1) CommentsController POST create create a new comment with valid
attributes
Failure/Error: post :create, comment: comment_attr
ActionController::UrlGenerationError:
No route matches {:comment=>{:body=>"This is a some comment ^_^", :user=>"5", :hotel=>"1"}, :controller=>"comments",
:action=>"create"}
# ./spec/controllers/comments_controller_spec.rb:10:in block (4 levels) in <top (required)>'
# ./spec/controllers/comments_controller_spec.rb:8:inblock (3 levels) in '
2) CommentsController POST create redirects to the new comment
Failure/Error: post :create, comment: comment_attr
ActionController::UrlGenerationError:
No route matches {:comment=>{:body=>"This is a some comment ^_^", :user=>"5", :hotel=>"1"}, :controller=>"comments",
:action=>"create"}
# ./spec/controllers/comments_controller_spec.rb:16:in `block (3 levels) in '
I think your main problem is that you send to action already created object
post :create, comment: Comment.create(comment_attr)
Rails parses params and fetches id of record and trying to find member route.
It looks like you would like to test that a new Comment will be created from params in controller. And it is nested resource so you should send hotel_id as well
Try to send
it 'creates a new comment with valid attributes' do
comment_attr = attributes_for(:comment)
hotel = Hotel.last || create(:hotel)
expect{
post :create, comment: comment_attr, hotel_id: hotel.id
}.to change(Comment,:count).by(1)
end
Another thing which confuses me is line post 'comment', only: :create in routes. I have never seen option only for post - usually it is used for resources.
I have a nested resource specialty under user.
My routes.rb looks like
resources :users do
resources :specialties do
end
end
My factories.rb looks like
Factory.define :user do |f|
f.description { Populator.sentences(1..3) }
f.experience { Populator.sentences(1..5) }
f.tag_list { create_tags }
end
Factory.define :specialty do |f|
f.association :user
specialties = CategoryType.valuesForTest
f.sequence(:category) { |i| specialties[i%specialties.length] }
f.description { Populator.sentences(1..5) }
f.rate 150.0
f.position { Populator.sentences(1) }
f.company { Populator.sentences(1) }
f.tag_list { create_tags }
end
My Specialties_controller.rb looks like
class SpecialtiesController < ApplicationController
def index
#user = User.find(params[:user_id])
#specialties = #user.specialties
respond_to do |format|
format.html # index.html.erb
format.json { render json: #specialties }
end
end
My specialties_controller_spec.rb looks like
require 'spec_helper'
describe SpecialtiesController do
render_views
describe "GET 'index'" do
before do
#user = Factory.create(:user)
#specialty = Factory.create(:specialty, :user => #user)
#user.stub!(:specialty).and_return(#specialty)
User.stub!(:find).and_return(#user)
end
def do_get
get :index, :user_id => #user.id
end
it "should render index template" do
do_get
response.should render_template('index')
end
it "should find user with params[:user_id]" do
User.should_receive(:find).with(#user.id.to_s).and_return(#user)
do_get
end
it "should get user's specialties" do
#user.should_receive(:specialty).and_return(#specialty)
do_get
end
end
end
The first two tests pass, but the last test fails with the error message
Failure/Error: #user.should_receive(:specialty).and_return(#specialty)
(#<User:0x007fe4913296a0>).specialty(any args)
expected: 1 time
received: 0 times
Does anybody have an idea what this error means and how to fix it? I've looked at similar posts and can't find an error in my code. Thanks in advance.
#user.should_receive(:specialty).and_return(#specialty)
specialtyis a one-to-many relation and should be plural: specialties. And indeed you have in your controller:
#specialties = #user.specialties