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)
对您的情况不起作用。
我的程序按预期运行,但我的 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)
对您的情况不起作用。