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

所以我的问题是:

  1. 当尝试这样写 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}"

  1. 我手动测试了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