I need to create RSpec testing for the following ruby code and seem to run into issues every time I try. I would love an example or two of RSpec tests that could be created for the following code/methods which are in my controller:
def edit
#movie = Movie.find params[:id]
end
def update
#movie = Movie.find params[:id]
#movie.update_attributes!(movie_params)
flash[:notice] = "#{#movie.title} was successfully updated."
redirect_to movie_path(#movie)
end
def destroy
#movie = Movie.find(params[:id])
#movie.destroy
flash[:notice] = "Movie '#{#movie.title}' deleted."
redirect_to movies_path
end
def find_with_same_director
#movie = Movie.find(params[:id])
#movies, check_info = Movie.find_with_same_director(params[:id])
if check_info
flash[:notice] = "'#{#movie.title}' has no director info"
redirect_to movies_path
end
end
I have this so far:
RSpec.describe MoviesController, type: :controller do
it 'should get all movies in the database' do
get :index
expect(response).to render_template :index
expect(assigns[:movies]).to eq(Movie.all)
end
describe 'find_with_same_director' do
it 'should call the find_with_same_director model method' do
expect(Movie).to receive(:find_with_same_director).with(no_args)
get :find_with_same_director, id: movie.id
end
end
end
but it is not covering it correctly. Any help would be appreciated, thanks.
RSpec.describe MoviesController, type: :controller do
it 'should get all movies in the database' do
get :index
expect(response).to render_template :index
expect(assigns[:movies]).to eq(Movie.all)
end
it 'should update movie in the database' do
Factory.girl.create(:movie)
before_movie_count = Movie.count
put :update, id: movie.id
expect(assigns[:movies]).to eq(...) # whatever data is passed
expect(Movie.count).to eq(before_ml_count)
expect(flash[:snack_class]).to eq(:notice.to_s)
expect(flash[:snack_message]).to ends_with('was successfully updated')
expect(response).to have_http_status(:redirect)
end
it 'should delete movie in the database' do
Factory.girl.create(:movie)
before_movie_count = Movie.count
delete :update, id: movie.id
expect(Movie.count).to eq(before_ml_count - 1)
expect(flash[:snack_class]).to eq(:notice.to_s)
expect(flash[:snack_message]).to ends_with('deleted')
expect(response).to have_http_status(:redirect)
end
describe 'find_with_same_director' do
it 'should call the find_with_same_director model method' do
expect(Movie).to receive(:find_with_same_director).with(no_args)
get :find_with_same_director, id: movie.id
end
end
end
#### in factory movie.rb
FactoryGirl.define do
factory :movie do
# assign data to movie attributes here.
end
end
Related
I am trying to write spec code for my controller it gets failed. And i am not sure where it gets failed.
Controller Code
def index
#users = User.all
end
def update
authorize! :update, #user
respond_to do |format|
if #user.update(user_params)
format.html { redirect_to user_index_path }
else
format.html { render :index }
end
end
end
private
def set_user
#user = User.find(params[:id])
end
def user_params
params.permit(:active)
end
Spec Code for the above controller
RSpec.describe UserController, type: :controller do
describe 'GET #index' do
let(:user) {User.create!(name: "hari")}
context 'with user details'do
it 'loads correct user details' do
get :index
expect(response).to permit(:user)
end
end
context 'without user details' do
it 'doesnot loads correct user details' do
get :index
expect(response).not_to permit(:user)
end
end
end
describe 'Patch #update' do
context 'when valid params' do
let(:attr) do
{active: 'true'}
end
before(:each) do
#user = subject.current_user
put :update, params: { user: attr }
#user.reload
end
it 'redirects to user_index_path ' do
expect(response).redirect_to(user_index_path)
end
it 'sets active state' do
expect(#user.active?('true')).to be true
end
end
context 'when invalid param' do
let(:attr) do
{active: 'nil'}
end
before(:each) do
#user = subject.current_user
put :update, params: { user: attr }
#user.reload
end
it 'render index' do
expect(respone.status).to eq(200)
end
it 'doesnot change active state' do
expect(#user.active?(nil)).to be true
end
end
end
end
I am just a beginner and tried the spec code for my controller by checking https://relishapp.com/rspec/rspec-rails/docs/gettingstarted. Can you help me where my spec goes wrong or could anyone give me a few test examples for these methods or could redirect me to an rspec guide? the index method is getting failed
and my
terminal log is
1) UserController GET #index with user details loads correct user details
Failure/Error: expect(response).to permit(:user)
NoMethodError:
undefined method `permit' for #<RSpec::ExampleGroups::UserController::GETIndex::WithUserDetails:0x00005614152406b0>
Did you mean? print
# ./spec/controllers/user_controller_spec.rb:10:in `block (4 levels) in <top (required)>'
In my small app , users can post comments. These comments can be destroyed only by their owners. I am trying to log in a user, create a comment, log out a user and then try to delete the comment that the first user created. However this action succeds for some reason. This is my comments controllor only showing the create and update actions and private methods:
module Api
module V1
class CommentsController < Api::V1::BaseController
before_action :check_user
before_action :get_comment, only: [:destroy, :update]
respond_to :json
def destroy
if #comment.destroy
head :no_content, status: :no_content
else
render json: serialize_model(#comment.errors)
end
end
def update
if #comment.update_attributes(comment_params)
render json: serialize_model(#comment), status: :accepted
else
render json: { errors: #comment.errors }, status: :bad_request
end
end
private
def comment_params
params.require(:comment).permit(:text, :picture_id)
end
def get_comment
#comment = Comment.find_by_id(params[:id])
check_owner
end
def check_user
render json: { errors: { user: "not signed in" } }, status: :unauthorized unless user_signed_in?
end
def check_owner
render json: { errors: { user: "not the owner" } }, status: :unauthorized unless current_user.id = #comment.id
end
end
end
end
These are my shared exmples for the test:
shared_context 'comments' do
def setup_requirements_without_login
#user = FactoryGirl.create(:user)
#category = FactoryGirl.create(:category)
#picture = FactoryGirl.create(:picture, category_id: #category.id, user_id: #user.id)
end
def setup_requirements_with_login
setup_requirements_without_login
sign_in(#user)
end
shared_examples 'not the owner' do
it 'creates a resource' do
body = JSON.parse(response.body)
expect(body).to include('errors')
data = body['errors']
expect(data).to include('user')
end
it 'responds with 401' do
expect(response).to have_http_status(401)
end
end
end
And these are the tests for update and destroy action:
require "rails_helper"
include Warden::Test::Helpers
Warden.test_mode!
RSpec.describe Api::V1::CommentsController, type: :controller do
include_context 'comments'
describe 'PATCH /api/comments/:id' do
context 'when it is a valid request' do
let(:attr) do
{ text: 'update' }
end
before(:each) do
setup_requirements_with_login
#comment = FactoryGirl.create(:comment, picture_id: #picture.id, user_id: #user.id)
patch :update, id: #comment.id, comment: attr , format: :json
end
it 'creates a resource' do
body = JSON.parse(response.body)
expect(body).to include('data')
data = body['data']
expect(data['attributes']['text']).to eq('update')
end
it 'responds with 202' do
expect(response).to have_http_status(202)
end
end
context 'when the user is not logged in' do
let(:attr) do
{ text: 'update' }
end
before(:each) do
setup_requirements_without_login
#comment = FactoryGirl.create(:comment, picture_id: #picture.id, user_id: #user.id)
patch :update, id: #comment.id, comment: attr , format: :json
end
it_behaves_like "not logged in"
end
context 'when the user is not the owner' do
let(:attr) do
{ text: 'update' }
end
before(:each) do
setup_requirements_with_login
#comment = FactoryGirl.create(:comment, picture_id: #picture.id, user_id: #user.id)
sign_out(#user)
logout
#user2 = FactoryGirl.create(:user)
sign_in(#user2)
patch :update, id: #comment.id, comment: attr , format: :json
end
it_behaves_like "not the owner"
end
end
describe 'DELETE /api/comments/:id' do
context 'when it is a valid request' do
before(:each) do
setup_requirements_with_login
#comment = FactoryGirl.create(:comment, picture_id: #picture.id, user_id: #user.id)
delete :destroy, id: #comment.id, format: :json
end
it 'responds with 204' do
expect(response).to have_http_status(204)
end
end
context 'when the user is not logged in' do
before(:each) do
setup_requirements_without_login
#comment = FactoryGirl.create(:comment, picture_id: #picture.id, user_id: #user.id)
delete :destroy, id: #comment.id, format: :json
end
it_behaves_like "not logged in"
end
context 'when the user is not the owner' do
before(:each) do
setup_requirements_with_login
#comment = FactoryGirl.create(:comment, picture_id: #picture.id, user_id: #user.id)
sign_out(#user)
logout
#user2 = FactoryGirl.create(:user)
sign_in(#user2)
delete :destroy, id: #comment.id, format: :json
end
it_behaves_like "not the owner"
end
end
end
My problem is that the action succeeds when it shouldn't for some reason. I use pry to debugg and it makes me question the tests even more because it says current_user has the id of 97 when the test created users with the ids: 1001 and 1002 which is very odd... . Did I make a mistake in the controller ? or tests?
your check_owner function should have == instead of = in its unless condition:
unless current_user.id == #comment.id
Otherwise the id from the #comment gets assigned to current_user.id. This is probably the origin for your 97. =)
def get_comment
#comment = current_user.comments.find! params[:id]
end
This automatically adds the association to the SQL query (where user_id=1337) and the bang method (with the !) throws an 404 Exception if record wasnt found. That is the easiest way to controll that only the owner has access to its own records.
post_controller file
class PostsController < ActionController::Base
before_action :authenticate_user!
def index
#post = current_user.posts.paginate(page: params[:page], per_page: 5)
respond_to do |format|
format.html
format.json { render json: #post }
end
end
def new
#post = Post.new
end
def create
#post = current_user.posts.build(post_param)
if #post.save
redirect_to action: 'index'
else
render 'new'
end
post_controller_test
require 'test_helper'
class PostsControllerTest < ActionController::TestCase
include Devise::TestHelpers
def setup
#user = users(:Bob)
#post = Post.new
end #passed
test 'logged in should get show' do
sign_in #user
get :index
assert_response :success
end #passed
test 'not authenticated should get redirect' do
get :index
assert_response :redirect
end #passed
test 'should get index' do
get :index
assert_response :success
assert_not_nil assigns(:posts)
end #failing
test "should destroy post" do
assert_difference('Post.count', -1) do
delete :destroy, id: #post
end
assert_redirected_to posts_path
end #failing
...
devise is setup and working fine but why I am getting 302 error in last two cases. Is it because I am not passing #user parameters to it? I did but it was still throwing the same error. I also checked out my routes file which is fine because post_controller is working fine in development mode.
What I am doing wrong here?
Edit-1
I tried to create test cases for create method
def setup
#user = users(:bob)
#p = posts(:one)
#post = Post.new
end
test 'should create post' do
sign_in #user
assert_difference('Post.count') do
post :create, post: { name: #p.name, value: #p.value}
end
end
I am getting ActionController::ParameterMissing: param is missing or the value is empty: post while in my controller class I do have
params.require(:post).permit(:name, :value, :user_id)
I also have all parameters in my .yml file i.e.
one:
name: 2
value: 3
It looks like you need to sign in before trying the index action. You're also testing the wrong instance variable name. You're testing #posts, but you've defined #post in the controller. Try this test instead:
test 'should get index' do
sign_in #user
get :index
assert_response :success
assert_not_nil assigns(:post)
end
I am building a simple blog app in order to learn BDD/TDD with RSpec and Factory Girl. Through this process, I continue to run into 'Failures' but I believe they have more to do with how I am using Factory Girl than anything.
As you'll see below, in order to get my specs to pass, I'm having a hard time keeping my test DRY - there must be something I am misunderstanding. You'll notice, I'm not using Factory Girl to it's full potential and at times, skipping it altogether. I find that I commonly run into problems when using functions such as get :create, get :show, or put :update within the spec.
I am currently stuck on the #PUT update spec that should simply test the assignment of the #post variable. I have tried multiple types of this spec that I found online, yet none seem to work - hence, is it Factory Girl? Maybe the specs I'm finding online are outdated Rspec versions?
I'm using:
Rspec 3.1.7
Rails 4.1.6
posts_controller_spec.rb
require 'rails_helper'
require 'shoulda-matchers'
RSpec.describe PostsController, :type => :controller do
describe "#GET index" do
it 'renders the index template' do
get :index
expect(response).to be_success
end
it "assigns all posts as #posts" do
post = Post.create(title: 'Charlie boy', body: 'Bow wow wow ruff')
get :index
expect(assigns(:posts)).to eq([post])
end
end
describe '#GET show' do
it 'assigns the request post to #post' do
post = Post.create!(title: 'Charlie boy', body: 'Bow wow wow ruff')
get :show, id: post.id
expect(assigns(:post)).to eq(post)
end
end
describe '#GET create' do
context 'with valid attributes' do
before :each do
post :create, post: attributes_for(:post)
end
it 'creates the post' do
expect(Post.count).to eq(1)
expect(flash[:notice]).to eq('Your post has been saved!')
end
it 'assigns a newly created post as #post' do
expect(assigns(:post)).to be_a(Post)
expect(assigns(:post)).to be_persisted
end
it 'redirects to the "show" action for the new post' do
expect(response).to redirect_to Post.first
end
end
context 'with invalid attributes' do
before :each do
post :create, post: attributes_for(:post, title: 'ha')
end
it 'fails to create a post' do
expect(Post.count).to_not eq(1)
expect(flash[:notice]).to eq('There was an error saving your post.')
end
it 'redirects to the "new" action' do
expect(response).to redirect_to new_post_path
end
end
end
describe '#GET edit' do
it 'assigns the request post to #post' do
post = Post.create!(title: 'Charlie boy', body: 'Bow wow wow ruff')
get :edit, id: post.id
expect(assigns(:post)).to eq(post)
end
end
describe '#PUT update' do
context 'with success' do
before :each do
post :create, post: attributes_for(:post)
end
it 'assigns the post to #post' do
put :update, id: post.id
expect(assigns(:post)).to eq(post)
end
end
end
end
posts_controller.rb
class PostsController < ApplicationController
def index
#posts = Post.all.order('created_at DESC')
end
def new
#post = Post.new
end
def create
#post = Post.new(post_params)
if #post.save
flash[:notice] = "Your post has been saved!"
redirect_to #post
else
flash[:notice] = "There was an error saving your post."
redirect_to new_post_path
end
end
def show
#post = Post.find(params[:id])
end
def edit
#post = Post.find(params[:id])
end
def update
#post = Post.find(params[:id])
# if #post.update(params[:post].permit(:title, :body))
# flash[:notice] = "Your post is updated!"
# redirect_to #post
# else
# flash[:notice] = "There was an error updating your post."
# render :edit
# end
end
private
def post_params
params.require(:post).permit(:title, :body)
end
end
factories/post.rb
FactoryGirl.define do
factory :post do
title 'First title ever'
body 'Forage paleo aesthetic food truck. Bespoke gastropub pork belly, tattooed readymade chambray keffiyeh Truffaut ennui trust fund you probably haven\'t heard of them tousled.'
end
end
Current Failure:
Failures:
1) PostsController#PUT update with success assigns the post to #post
Failure/Error: put :update, id: post.id
ArgumentError:
wrong number of arguments (0 for 1+)
# ./spec/controllers/posts_controller_spec.rb:86:in `block (4 levels) in <top (required)>'
Finished in 0.19137 seconds (files took 1.17 seconds to load)
17 examples, 1 failure
You could definitely leverage factories here.
The factory you've created is actually fine too.
Instead of doing:
post = Post.create(title: 'Charlie boy', body: 'Bow wow wow ruff')
Do this: post = FactoryGirl.create(:post)
You can get ever more DRY if you do this:
# in spec/rails_helper.rb
RSpec.configure do |config|
config.include FactoryGirl::Syntax::Methods
end
This will allow you do this in your spec: post = create(:post)
Regarding your PUT test, try this from a previous SO answer:
describe '#PUT update' do
let(:attr) do
{ :title => 'new title', :content => 'new content' }
end
context 'with success' do
before :each do
#post = FactoryGirl.create(:post)
end
it 'assigns the post to #post' do
put :update, :id => #post.id, :post => attr
#post.reload
expect(assigns(:post)).to eq(post)
end
end
end
Edit:
Also, don't be afraid of moving things in to a before :each do if you need to. They are great at keeping things DRY
The immediate reason why your spec is failing is because you can only call on the controller once per test, and for update you're calling it twice: in the before-action, you are calling create... and then in the main part of the update test you are calling update... controller specs don't like that.
In order to get the existing spec working, you would need to replace the post :create, post: attributes_for(:post) line in the before-action with just creating a post or (as mentioned already) using factory girl to create a post - rather than trying to do it by calling the controller to do it.
I'm building a project to learn Rails and testing, and struggling to troubleshoot errors in an RSpec test of a controller that directs a nested resource. My code works as expected in a browser. I believe the problem relates to my test set-up and the associations of FactoryGirl objects. I need help troubleshooting and fixing the controller spec.
Here's the cardio_exercises_controller.rb
class CardioExercisesController < ApplicationController
# :get_member is defined in the private method at the bottom of this file,
# and takes the member_id provided by the routing and
#converts it to a #member object.
before_action :get_member
# GET member/1/cardio_exercises
# GET member/1/cardio_exercises.json
def index
#cardio_exercises = #member.cardio_exercises
end
# GET member/1/cardio_exercises/1
# GET member/1/cardio_exercises/1.json
def show
cardio_exercise = #member.cardio_exercises.find(params[:id])
end
# GET member/1/cardio_exercises/new
def new
#member = Member.find(params[:member_id])
#cardio_exercise = #member.cardio_exercises.build
end
# GET member/1/cardio_exercises/1/edit
def edit
#cardio_exercise = #member.cardio_exercises.find(params[:id])
end
# POST member/1/cardio_exercises
# POST member/1/cardio_exercises.json
def create
#cardio_exercise = #member.cardio_exercises.build(cardio_exercise_params)
if #cardio_exercise.save
flash[:success] = "Cardio exercise was successfully created."
redirect_to member_cardio_exercises_path(#member)
else
render 'new'
end
end
# PATCH/PUT member/1/cardio_exercises/1
# PATCH/PUT member/1/cardio_exercises/1.json
def update
#cardio_exercise = #member.cardio_exercises.find(params[:id])
if #cardio_exercise.update(cardio_exercise_params)
flash[:success] = "Cardio exercise was successfully updated."
redirect_to member_cardio_exercises_path(#member)
else
render 'edit'
end
end
# DELETE member/1/cardio_exercises/1
# DELETE member/1/cardio_exercises/1.json
def destroy
#cardio_exercise = #member.cardio_exercises.find(params[:id])
#cardio_exercise.destroy
respond_to do |format|
format.html { redirect_to (member_cardio_exercises_path(#member)), notice: 'Cardio exercise was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# The get_member action converts the member_id given by the routing
# into an #member object, for use here and in the view.
def get_member
#member = Member.find(params[:member_id])
end
def cardio_exercise_params
params.require(:cardio_exercise).permit(:title, :duration, :calories_burned, :date, :member_id)
end
end
Here's the cardio_exercises_controller_spec.rb
require 'rails_helper'
RSpec.describe CardioExercisesController, :type => :controller do
before :each do
#member = FactoryGirl.create(:member)
#cardio_exercise = FactoryGirl.create(:cardio_exercise)
#cardio_exercise_attributes = FactoryGirl.attributes_for(:cardio_exercise, :member_id => #member)
end
describe "GET index" do
it "assigns all cardio_exercises as #member.cardio_exercises" do
get :index, { :member_id => #member }
expect(assigns(:cardio_exercises)).to eq(#member.cardio_exercises)
end
end
describe "GET show" do
it "assigns the requested cardio_exercise as #member.cardio_exercise" do
get :show, { :member_id => #member, :id => #cardio_exercise }
expect(assigns(:cardio_exercise)).to eq(#member.cardio_exercise)
end
end
describe "GET new" do
it "assigns a new cardio_exercise as #member.cardio_exercise" do
get :new, { :member_id => #member }
expect(assigns(:cardio_exercise)).to be_a_new(CardioExercise)
end
end
describe "GET edit" do
it "assigns the requested cardio_exercise as #member.cardio_exercise" do
end
end
describe "POST create" do
describe "with valid params" do
it "creates a new CardioExercise" do
expect {
post :create, { :member_id => #member, :cardio_exercise => #cardio_exercise_attributes }
}.to change(CardioExercise, :count).by(1)
end
it "assigns a newly created cardio_exercise as #cardio_exercise" do
post :create, { :member_id => #member, :cardio_exercise => #cardio_exercise_attributes }
expect(assigns(:cardio_exercise)).to be_a(CardioExercise)
expect(assigns(:cardio_exercise)).to be_persisted
end
it "redirects to the created cardio_exercise" do
post :create, { :member_id => #member, :cardio_exercise => #cardio_exercise_attributes }
expect(response).to redirect_to(CardioExercise.last)
end
end
end
describe "PUT update" do
describe "with invalid params" do
xit "updates the requested cardio_exercise" do
#put :update, { id: #member.id, member_id: cardio_exercise: #cardio_exercise.id }
end
xit "assigns the requested cardio_exercise as #member.cardio_exercise" do
end
xit "redirects to the cardio_exercise" do
end
end
describe "with invalid params" do
xit "assigns the cardio_exercise as #member.cardio_exercise" do
end
xit "re-renders the 'edit' template" do
expect(response).to render_template("edit")
end
end
end
describe "DELETE destroy" do
it "destroys the requested cardio_exercise" do
expect {
delete :destroy, { :member_id => #member, :id => #cardio_exercise }
}.to change(CardioExercise, :count).by(-1)
end
it "redirects to the cardio_exercises list" do
delete :destroy, { :member_id => #member, :id => #cardio_exercise }
expect(response).to redirect_to(member_cardio_exercises_url)
end
end
end
These are the relevant factories:
FactoryGirl.define do
factory :cardio_exercise do
title "My cardio exercise"
duration 30
calories_burned 300
date "2014-11-15"
association :member
end
end
FactoryGirl.define do
factory :member do
first_name {Faker::Name.first_name}
last_name {Faker::Name.last_name}
age 21
height 75
weight 195
goal "fffff" * 5
start_date "2014-11-15"
end
end
routes.rb contains:
resources :members do
resources :cardio_exercises
end
members.rb contains:
has_many :cardio_exercises, :dependent => :destroy
cardio_exercises.rb contains:
belongs_to :member
Rspec Failures/Errors:
1) CardioExercisesController GET show assigns the requested cardio_exercise as #member.cardio_exercise
Failure/Error: get :show, { :member_id => #member, :id => #cardio_exercise }
ActiveRecord::RecordNotFound:
Couldn't find CardioExercise with 'id'=25 [WHERE "cardio_exercises"."member_id" = $1]
2) CardioExercisesController POST create with valid params redirects to the created cardio_exercise
Failure/Error: expect(response).to redirect_to(CardioExercise.last)
NoMethodError:
undefined method `cardio_exercise_url' for #<CardioExercisesController:0x00000008bba960>
3) CardioExercisesController DELETE destroy destroys the requested cardio_exercise
Failure/Error: delete :destroy, { :member_id => #member, :id => #cardio_exercise }
ActiveRecord::RecordNotFound:
Couldn't find CardioExercise with 'id'=34 [WHERE "cardio_exercises"."member_id" = $1]
4) CardioExercisesController DELETE destroy redirects to the cardio_exercises list
Failure/Error: delete :destroy, { :member_id => #member, :id => #cardio_exercise }
ActiveRecord::RecordNotFound:
Couldn't find CardioExercise with 'id'=35 [WHERE "cardio_exercises"."member_id" = $1]
I think the Record Not Found errors indicate a problem with the associations between the member and cardio exercise models. The controller isn't finding the cardio exercise by its id. What have I missed in setting things up for RSpec? What's the best way to fix the set up?
The Undefined method error appears to be caused by my calling the last method on CardioExercise. I'm posting to the create method in the example. CardioExercise is the class. Can someone explain why that call triggers the error, and how to fix it?
I appreciate any help!
You create two independent objects/records: member and cardio_exercise.
You should pass #member to factory cardio_exercise to connect them.
#member = FactoryGirl.create(:member)
#cardio_exercise = FactoryGirl.create(:cardio_exercise, member: #member)
PS When you create cardio_exercise without setup member factory creates new record in table members and assign cardio_exercise with this new record
UPDATE
about "GET index"
You create #member without any associated cardio_exercises. You added them later and object #member knows nothing about it. You should reload object to fetch data from DB
expect(assigns(:cardio_exercises)).to eq(#member.reload.cardio_exercises)
and sometimes I convert relations to array and sort result to avoid failing tests when order is different