【问题标题】:RSpec controller tests of PUT update action on nested resourceRSpec 控制器测试嵌套资源上的 PUT 更新操作
【发布时间】:2025-12-23 05:50:16
【问题描述】:

我正在学习 Rails 以及如何使用 RSpec 进行测试。我需要帮助完成 PUT 更新操作的 RSpec 控制器测试。控制器指挥嵌套资源。嵌套路由和相关程序代码在 Web 浏览器中按预期工作。我只需要帮助编写 PUT 更新操作的 RSpec 测试。我已经通过了控制器执行的其他操作的测试。

我知道必须将路径以及父 ID 和子 ID 传递给 put 方法,但我对以下问题感到困惑:(1)如何使用 FactoryGirl 对象正确设置 PUT 更新测试,以及(2 ) 正确的 RSpec 语法将路径和 ID 传递到 PUT 方法。

rake routes 中的相关路由:

  PATCH  /members/:member_id/cardio_exercises/:id(.:format)      cardio_exercises#update
  PUT    /members/:member_id/cardio_exercises/:id(.:format)      cardio_exercises#update

routes.rb 包含: 资源:成员做 资源:cardio_exercises 结束

members.rb 包含: has_many :cardio_exercises, :dependent => :destroy

cardio_exercises.rb 包含: 属于_to:成员

相关工厂:

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

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

cardio_exercises_controller_spec.rb 和我尝试设置 PUT 更新操作的几个测试:

require 'rails_helper'

RSpec.describe CardioExercisesController, :type => :controller do
  before :each do
    @member = FactoryGirl.create(:member)      
    @cardio_exercise = FactoryGirl.create(:cardio_exercise, member: @member)      
    @cardio_exercise_attributes = FactoryGirl.attributes_for(:cardio_exercise, :member_id => @member)
    @cardio_exercise_update_attributes = FactoryGirl.attributes_for(:cardio_exercise, :title => "Your 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.reload.cardio_exercises)
end
end

describe "GET show" do
it "assigns the requested cardio_exercise as @cardio_exercise" do
  get :show, { :member_id => @member, :id => @cardio_exercise }    
  expect(assigns(:cardio_exercise)).to eq(@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      
  get :edit, { :member_id => @member, :id => @cardio_exercise }
  expect(assigns(:cardio_exercise)).to eq(@cardio_exercise) 
 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(member_cardio_exercises_path(@member))
  end
  end
 end

 describe "PUT update" do
  describe "with valid params" do

  it "updates the requested cardio_exercise" do
    put :update, { :member_id => @member, :id => @cardio_exercise_update_attributes }
    @member.cardio_exercise.reload                      
    put :update, { :member_id => @member, :id => @cardio_exercise_update_attributes}

  end

  it "assigns the requested cardio_exercise as @member.cardio_exercise" do
    put :update, { :member_id => @member, :cardio_exercise => @cardio_exercise_update_attributes = FactoryGirl.attributes_for(:cardio_exercise, :title => "Your Cardio Exercise", :member_id => @member)}
    expect(assigns(:cardio_exercises)).to eq(@member.cardio_exercises)
  end

  it "redirects to the cardio_exercise" do 
    put :update, { :member_id => @member, :id => @cardio_exercise }        
    expect(response).to redirect_to(@member.cardio_exercise)
  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

请查看我的测试设置和 PUT 更新测试。具体来说,我的设置是测试 PUT 更新操作的正确方法吗?

如何更改控制器更新操作的设置和/或测试,以便:

(1) 我的测试与我的更新操作路线匹配?

(2) 我以正确的 RSpec 语法传递了必要的父 ID 和子 ID,以便可以找到记录?

感谢您的帮助!

【问题讨论】:

  • 您是否尝试在测试中将put 更改为patch?请记住,Rails 4 使用 PATCH 进行更新调用。
  • 尝试了补丁,但生成相同的记录未找到/路由不匹配错误。问题似乎与我为更新操作设置测试的方式有关。

标签: ruby-on-rails rspec factory-bot


【解决方案1】:

您必须传递member_id、cardio_exercise 的idcardio_exercise 参数才能更新:

put :update, { 
  :member_id => @member, 
  :id => @cardio_exercise.id, 
  :cardio_exercise => @cardio_exercise_update_attributes 
}

【讨论】:

  • 感谢@infused,添加cardio_exercise 参数会导致第一个PUT 测试通过。不幸的是,第二个和第三个测试失败,出现未定义的方法错误 `cardio_exercise' for #<0x00000007778c40>