codeinspired
codeinspired

Reputation: 366

Failing RSpec tests of Controller with nested resource and associated FactoryGirl objects

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!

Upvotes: 0

Views: 699

Answers (1)

gotva
gotva

Reputation: 5998

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

Upvotes: 2

Related Questions