RSpec POST 创建的控制器测试失败

RSpec Controller tests fail for POST create

我的程序按预期运行,但我的 RSpec 控制器测试代码存在问题。我需要帮助解决规范问题,这些规范是在我 运行 脚手架生成器时创建的。有 3 次 "POST create with valid params" 失败:

POST create
with valid params
  creates a new Appointment (FAILED - 1)
  assigns a newly created appointment as @appointment (FAILED - 2)
  redirects to the created appointment (FAILED - 3)

Failures:

1) AppointmentsController POST create with valid params creates a new Appointment
 Failure/Error: expect {
   expected #count to have changed by 1, but was changed by 0
 # ./spec/controllers/appointments_controller_spec.rb:90:in `block (4 levels) in <top (required)>'

2) AppointmentsController POST create with valid params assigns a newly created appointment as @appointment
 Failure/Error: expect(assigns(:appointment)).to be_persisted
   expected `#<Appointment id: nil, member_id: nil, trainer_id: nil, created_at: nil, updated_at: nil, date: "2020-01-02", starts_at: "2000-01-01 08:00:00", ends_at: "2000-01-01 09:00:00">.persisted?` to return true, got false
 # ./spec/controllers/appointments_controller_spec.rb:99:in `block (4 levels) in <top (required)>'

3) AppointmentsController POST create with valid params redirects to the created appointment
 Failure/Error: expect(response).to redirect_to(Appointment.last)
   Expected response to be a <redirect>, but was <200>
 # ./spec/controllers/appointments_controller_spec.rb:104:in `block (4 levels) in <top (required)>'

在第二次失败中,我注意到约会 ID、成员和培训师的值为 nil,尽管我有成员和培训师的有效工厂。我的成员工厂和培训师工厂的测试通过了,并且它们按预期工作。我认为问题一定是由我在 Controller 规范中设置 "valid attributes" 散列的方式引起的,但我不知道出了什么问题。为什么 POST 创建测试失败?我需要做什么才能让他们通过?

这是约会控制器的代码RSpec:

require 'rails_helper'


RSpec.describe AppointmentsController, :type => :controller do

let(:valid_attributes) { {
'date' => '2020-01-02',
'starts_at' => '08:00:00',
'ends_at' => '09:00:00',    
'member' => FactoryGirl.build(:member),
'trainer' => FactoryGirl.build(:trainer) 
}

}

let(:invalid_attributes) { {
'date' => '2000-01-02',
'starts_at' => '06:00:00',
'ends_at' => '09:00:00',
'member' => FactoryGirl.build(:member),
'trainer' => FactoryGirl.build(:trainer)    
}
}

let(:valid_session) { {
'date' => '2020-12-30',
'starts_at' => '15:00:00',
'ends_at' => '17:00:00', 
'member' => FactoryGirl.build(:member),
'trainer' => FactoryGirl.build(:trainer) 
}
}

describe "GET index" do
it "assigns all appointments as @appointments" do
  appointment = Appointment.create! valid_attributes
  get :index, {}, valid_session
  expect(assigns(:appointments)).to eq([appointment])
end
end

describe "GET show" do
it "assigns the requested appointment as @appointment" do
  appointment = Appointment.create! valid_attributes
  get :show, {:id => appointment.to_param}, valid_session
  expect(assigns(:appointment)).to eq(appointment)
end
end

describe "GET new" do
it "assigns a new appointment as @appointment" do
  get :new, {}, valid_session
  expect(assigns(:appointment)).to be_a_new(Appointment)
end
end

describe "GET edit" do
it "assigns the requested appointment as @appointment" do
  appointment = Appointment.create! valid_attributes
  get :edit, {:id => appointment.to_param}, valid_session
  expect(assigns(:appointment)).to eq(appointment)
end
end

describe "POST create" do
describe "with valid params" do
  it "creates a new Appointment" do
    expect {
      post :create, {:appointment => valid_attributes}, valid_session
    }.to change(Appointment, :count).by(1)
    save_and_open_page
  end

  it "assigns a newly created appointment as @appointment" do
    post :create, {:appointment => valid_attributes}, valid_session
    expect(assigns(:appointment)).to be_a(Appointment)
    expect(assigns(:appointment)).to be_persisted        
  end

  it "redirects to the created appointment" do
    post :create, {:appointment => valid_attributes}, valid_session
    expect(response).to redirect_to(Appointment.last)        
  end
  end

describe "with invalid params" do
  it "assigns a newly created but unsaved appointment as @appointment" do
    post :create, {:appointment => invalid_attributes}, valid_session
    expect(assigns(:appointment)).to be_a_new(Appointment)
  end

  it "re-renders the 'new' template" do
    post :create, {:appointment => invalid_attributes}, valid_session
    expect(response).to render_template("new")
  end
 end
end

describe "PUT update" do
describe "with valid params" do
  let(:new_attributes) { {
    'date' => '2020-01-02',
    'starts_at' => '10:00:00',
    'ends_at' => '12:00:00',
    'member' => FactoryGirl.build(:member),
    'trainer' => FactoryGirl.build(:trainer) 
   }
  }

  let(:invalid_attributes) { {
    'date' => '2005-03-15',
    'starts_at' => '04:00:00',
    'ends_at' => '09:00:00',
    'member' => FactoryGirl.build(:member),
    'trainer' => FactoryGirl.build(:trainer)    
  }
}

  it "updates the requested appointment" do
    appointment = Appointment.create! valid_attributes
    put :update, {:id => appointment.to_param, :appointment => new_attributes}, valid_session
    appointment.reload
    expect(controller.notice).to eq('Appointment was successfully updated.')
  end

  it "assigns the requested appointment as @appointment" do
    appointment = Appointment.create! valid_attributes
    put :update, {:id => appointment.to_param, :appointment => valid_attributes}, valid_session
    expect(assigns(:appointment)).to eq(appointment)
  end

  it "redirects to the appointment" do
    appointment = Appointment.create! valid_attributes
    put :update, {:id => appointment.to_param, :appointment => valid_attributes}, valid_session
    expect(response).to redirect_to(appointment)
  end
end

describe "with invalid params" do
  it "assigns the appointment as @appointment" do
    appointment = Appointment.create! valid_attributes
    put :update, {:id => appointment.to_param, :appointment => invalid_attributes}, valid_session
    expect(assigns(:appointment)).to eq(appointment)
  end

  it "re-renders the 'edit' template" do
    appointment = Appointment.create! valid_attributes
    put :update, {:id => appointment.to_param, :appointment => invalid_attributes}, valid_session
    expect(response).to render_template("edit")
    end
  end
 end

describe "DELETE destroy" do
 it "destroys the requested appointment" do
  appointment = Appointment.create! valid_attributes
  expect {
    delete :destroy, {:id => appointment.to_param}, valid_session
  }.to change(Appointment, :count).by(-1)
end

it "redirects to the appointments list" do
  appointment = Appointment.create! valid_attributes
  delete :destroy, {:id => appointment.to_param}, valid_session
  expect(response).to redirect_to(appointments_url)
  end
 end
end

这是约会控制器的代码:

class AppointmentsController < ApplicationController
before_action :set_appointment, only: [:show, :edit, :update, :destroy]

# GET /appointments
# GET /appointments.json
def index
@appointments = Appointment.all
end

# GET /appointments/1
# GET /appointments/1.json
def show
end

# GET /appointments/new
def new
@appointment = Appointment.new
end

# GET /appointments/1/edit
def edit

结束

# POST /appointments
# POST /appointments.json
def create
@appointment = Appointment.new(appointment_params)

respond_to do |format|
  if @appointment.save
    format.html { redirect_to @appointment, notice: 'Appointment was successfully created.' }
    format.json { render :show, status: :created, location: @appointment }
  else
    format.html { render :new }
    format.json { render json: @appointment.errors, status: :unprocessable_entity }
  end
 end
end

# PATCH/PUT /appointments/1
# PATCH/PUT /appointments/1.json
def update
respond_to do |format|
  if @appointment.update(appointment_params)
    format.html { redirect_to @appointment, notice: 'Appointment was successfully updated.' }
    format.json { render :show, status: :ok, location: @appointment }
  else
    format.html { render :edit }
    format.json { render json: @appointment.errors, status: :unprocessable_entity }
  end
 end
end

# DELETE /appointments/1
# DELETE /appointments/1.json
def destroy
@appointment.destroy
respond_to do |format|
  format.html { redirect_to appointments_url, notice: 'Appointment was successfully destroyed.' }
  format.json { head :no_content }
 end
end

private
# Use callbacks to share common setup or constraints between actions.
def set_appointment
  @appointment = Appointment.find(params[:id])
end

# Never trust parameters from the scary internet, only allow the white list through.
def appointment_params
  params.require(:appointment).permit(:date, :starts_at, :ends_at, :member_id, :trainer_id)
end
end

预约模型的代码:

class Appointment < ActiveRecord::Base

belongs_to :member
belongs_to :trainer

validates_date :date, :on => :create, :on_or_after => :today

validates_time :starts_at, :between => ['06:30', '21:00']
validates_time :starts_at, :after => :now, :if => :today_appointment #scopes validation to current day only  
validates_time :ends_at, :after => :starts_at

validate :duration_of_appointment

validates :member, :trainer, presence: true

validates :starts_at, :ends_at, :overlap => {
:exclude_edges => ["starts_at", "ends_at"],
:scope => "date",
:scope => "starts_at",    
:scope => "trainer_id"    
}

validates :starts_at, :ends_at, :overlap => {
:exclude_edges => ["starts_at", "ends_at"],
:scope => "member_id"
}    


private

def today_appointment
Date.current == self.date     
end  


def duration_of_appointment
length = (ends_at - starts_at) / 60
return if length.between?(30, 120) # stops validation if length falls between the two integers
errors.add(:base, 'Duration must be between 30 and 120 minutes')
end
end

您不应该向控制器提供 Member 和 Trainer 的构建实例。相反,创建 Member 和 Trainer,并将他们的 id 作为 member_id 传递, trainer_id 在 valid_attributes 哈希中。 首先,创建所需的培训师和成员:

before :each do
  @trainer = FactoryGirl.build(:trainer)
  @member = FactoryGirl.build(:member)
end

然后在您的散列中使用他们的 ID:

let(:valid_attributes) { {
  'date' => '2020-01-02',
  'starts_at' => '08:00:00',
  'ends_at' => '09:00:00',    
  'member_id' => @member.id,
  'trainer_id' => @trainer.id 
}

在您的代码中,您甚至没有创建它们,您只是构建了它们,但仅将 build(:trainer) 更改为 create(:trainer) 对您的情况不起作用。