RSpec 的测试更新请求失败
test update request with RSpec failed
当我尝试使用 RSpec 测试更新操作时遇到两个问题,这里是 controller
文件:
#volunteers_controller.rb
module Api
module V1
class VolunteersController < ApplicationController
before_action :find_volunteer, only: %i[show update destroy]
def update
@volunteer.update!(volunteer_params)
head :no_content
end
private
def find_volunteer
@volunteer = Volunteer.find_by!(id: params[:id])
end
def volunteer_params
params.require(:volunteer).permit(:image_url, :name, :job_desc)
end
end
end
end
这是 test
文件:
require 'rails_helper'
RSpec.describe Api::V1::VolunteersController, type: :request do
...
describe '#update' do
let(:volunteer) { Volunteer.create!( :image_url=>"first.jpg", :name=>"test1", :job_desc=>"description") }
let(:params){
{:volunteer => {
"image_url"=>"new.jpg",
"name"=>"test1",
"job_desc"=>"description"
}
}
}
it 'updates a certain volunteer' do
patch :patch, :params => params #failed, bad URL
expect(volunteer.image_url).to eq("new.jpg") #failed, still return 'first.jpg'
end
it 'returns a no_content header' do
patch "http://localhost:3000/api/v1/volunteers/#{volunteer.id}", :params => params
expect(response).to have_http_status "204"
end
end
end
private
def json_parse(string)
if string.class==String
json = JSON.parse(string)
end
json
end
所以我的问题是:
- 当尝试这样写 URL 时:
patch :patch, :params => params
,出现以下错误:
Api::V1::VolunteersController#update updates a certain volunteer
Failure/Error: patch :patch, :params => params
URI::InvalidURIError:
bad URI(is not URI?): "http://www.example.com:80patch"
如何将 URL 更改为:"http://localhost:3000/api/v1/volunteers/#{volunteer.id}"
?
- 我手动测试了update动作,在
update
动作中放了一个binding.pry
,它确实更新了volunteer
主题,但是,当它返回测试时,它显示它没有得到更新,这是为什么?
谢谢!!
第一个问题实际上是您的更新方法本身及其完全缺乏错误处理和对客户端有意义的反馈。如果输入无效,update!
将引发 ActiveRecord::RecordInvalid
- 这在您的控制器中根本没有被拯救。正常代码流不应使用异常 - 无效输入并不是真正的异常事件。
相反,您应该重写您的控制器,以便它检查是否执行了更新以及 returns 适当的响应:
def update
if @volunteer.update(volunteer_params)
head :no_content
else
head :unprocessable_entity
end
end
至于规范本身,您混淆了控制器规范和请求规范。虽然它们看起来有些相似,但主要区别在于请求规范向您的 rails 服务器发送实际的 HTTP 请求,而控制器规范将实际请求存根并将其传递给被测控制器的实例。
在控制器规范中你可以这样写:
patch :update, params: { ... }
因为它实际上是在控制器实例上调用更新方法。但当然:
patch :patch, :params => params #failed, bad URL
在请求规范中不起作用,因为它不是有效的 URL 并且请求规范发送实际的 HTTP 请求。请注意,您应该传递相对 URLs 而不是绝对 URLs,因为测试服务器可能 运行 在与开发服务器
不同的端口上
# Bad
patch "http://localhost:3000/api/v1/volunteers/#{volunteer.id}", :params => params
# Good
patch "/api/v1/volunteers/#{volunteer.id}", params: params
ActiveRecord 模型不是 "live reloading" - 当数据库中的值更新时,内存中的表示不会自动更新。您需要手动重新加载记录才能发生:
it 'updates a certain volunteer' do
patch "/api/v1/volunteers/#{volunteer.id}", params: params
volunteer.reload
expect(volunteer.image_url).to eq("new.jpg")
end
总的来说,您的规格实际上应该类似于:
# Describe the endpoint - not the controller implmentation
RSpec.describe "V1 Volunteers API", type: :request do
describe 'PATCH /api/v1/volunteers/:id' do
# use do ... end if the expression does not fit on one line
let(:volunteer) do
# enough with the hashrockets already!
Volunteer.create!(
image_url: "first.jpg",
name: "test1",
job_desc: "description"
)
end
context "with invalid parameters" do
# some set of failing parameters
let(:params) do
{
volunteer: {
name: ""
}
}
end
it "returns unproccessable entity" do
patch "/api/v1/volunteers/#{volunteer.id}", params: params
expect(resonse).to have_http_status :unproccessable_entity
end
it "does not update the volunteer" do
patch "/api/v1/volunteers/#{volunteer.id}", params: params
expect { volunteer.reload }.to_not change(volunteer, :name).to("")
end
end
context "with valid parameters" do
# some set of failing parameters
let(:params) do
{
volunteer: {
image_url: "new.jpg",
name: "test1",
job_desc: "description"
}
}
end
it "returns no content" do
patch "/api/v1/volunteers/#{volunteer.id}", params: params
expect(resonse).to have_http_status :no_content
end
it "updates the volunteer" do
patch "/api/v1/volunteers/#{volunteer.id}", params: params
expect { volunteer.reload }.to change(volunteer, :image_url)
.to("new.jpg")
end
end
end
end
当我尝试使用 RSpec 测试更新操作时遇到两个问题,这里是 controller
文件:
#volunteers_controller.rb
module Api
module V1
class VolunteersController < ApplicationController
before_action :find_volunteer, only: %i[show update destroy]
def update
@volunteer.update!(volunteer_params)
head :no_content
end
private
def find_volunteer
@volunteer = Volunteer.find_by!(id: params[:id])
end
def volunteer_params
params.require(:volunteer).permit(:image_url, :name, :job_desc)
end
end
end
end
这是 test
文件:
require 'rails_helper'
RSpec.describe Api::V1::VolunteersController, type: :request do
...
describe '#update' do
let(:volunteer) { Volunteer.create!( :image_url=>"first.jpg", :name=>"test1", :job_desc=>"description") }
let(:params){
{:volunteer => {
"image_url"=>"new.jpg",
"name"=>"test1",
"job_desc"=>"description"
}
}
}
it 'updates a certain volunteer' do
patch :patch, :params => params #failed, bad URL
expect(volunteer.image_url).to eq("new.jpg") #failed, still return 'first.jpg'
end
it 'returns a no_content header' do
patch "http://localhost:3000/api/v1/volunteers/#{volunteer.id}", :params => params
expect(response).to have_http_status "204"
end
end
end
private
def json_parse(string)
if string.class==String
json = JSON.parse(string)
end
json
end
所以我的问题是:
- 当尝试这样写 URL 时:
patch :patch, :params => params
,出现以下错误:
Api::V1::VolunteersController#update updates a certain volunteer
Failure/Error: patch :patch, :params => params
URI::InvalidURIError:
bad URI(is not URI?): "http://www.example.com:80patch"
如何将 URL 更改为:"http://localhost:3000/api/v1/volunteers/#{volunteer.id}"
?
- 我手动测试了update动作,在
update
动作中放了一个binding.pry
,它确实更新了volunteer
主题,但是,当它返回测试时,它显示它没有得到更新,这是为什么?
谢谢!!
第一个问题实际上是您的更新方法本身及其完全缺乏错误处理和对客户端有意义的反馈。如果输入无效,update!
将引发 ActiveRecord::RecordInvalid
- 这在您的控制器中根本没有被拯救。正常代码流不应使用异常 - 无效输入并不是真正的异常事件。
相反,您应该重写您的控制器,以便它检查是否执行了更新以及 returns 适当的响应:
def update
if @volunteer.update(volunteer_params)
head :no_content
else
head :unprocessable_entity
end
end
至于规范本身,您混淆了控制器规范和请求规范。虽然它们看起来有些相似,但主要区别在于请求规范向您的 rails 服务器发送实际的 HTTP 请求,而控制器规范将实际请求存根并将其传递给被测控制器的实例。
在控制器规范中你可以这样写:
patch :update, params: { ... }
因为它实际上是在控制器实例上调用更新方法。但当然:
patch :patch, :params => params #failed, bad URL
在请求规范中不起作用,因为它不是有效的 URL 并且请求规范发送实际的 HTTP 请求。请注意,您应该传递相对 URLs 而不是绝对 URLs,因为测试服务器可能 运行 在与开发服务器
不同的端口上# Bad
patch "http://localhost:3000/api/v1/volunteers/#{volunteer.id}", :params => params
# Good
patch "/api/v1/volunteers/#{volunteer.id}", params: params
ActiveRecord 模型不是 "live reloading" - 当数据库中的值更新时,内存中的表示不会自动更新。您需要手动重新加载记录才能发生:
it 'updates a certain volunteer' do
patch "/api/v1/volunteers/#{volunteer.id}", params: params
volunteer.reload
expect(volunteer.image_url).to eq("new.jpg")
end
总的来说,您的规格实际上应该类似于:
# Describe the endpoint - not the controller implmentation
RSpec.describe "V1 Volunteers API", type: :request do
describe 'PATCH /api/v1/volunteers/:id' do
# use do ... end if the expression does not fit on one line
let(:volunteer) do
# enough with the hashrockets already!
Volunteer.create!(
image_url: "first.jpg",
name: "test1",
job_desc: "description"
)
end
context "with invalid parameters" do
# some set of failing parameters
let(:params) do
{
volunteer: {
name: ""
}
}
end
it "returns unproccessable entity" do
patch "/api/v1/volunteers/#{volunteer.id}", params: params
expect(resonse).to have_http_status :unproccessable_entity
end
it "does not update the volunteer" do
patch "/api/v1/volunteers/#{volunteer.id}", params: params
expect { volunteer.reload }.to_not change(volunteer, :name).to("")
end
end
context "with valid parameters" do
# some set of failing parameters
let(:params) do
{
volunteer: {
image_url: "new.jpg",
name: "test1",
job_desc: "description"
}
}
end
it "returns no content" do
patch "/api/v1/volunteers/#{volunteer.id}", params: params
expect(resonse).to have_http_status :no_content
end
it "updates the volunteer" do
patch "/api/v1/volunteers/#{volunteer.id}", params: params
expect { volunteer.reload }.to change(volunteer, :image_url)
.to("new.jpg")
end
end
end
end