【问题标题】:Testing Rails API controller POST with RSpec使用 RSpec 测试 Rails API 控制器 POST
【发布时间】:2017-10-20 01:48:35
【问题描述】:

正如标题所示,我只是想在我的 API 控制器中使用 RSpec 测试创建操作。控制器看起来像:

module Api
  module V1
    class BathroomController < ApplicationController
      skip_before_action :verify_authenticity_token, only: [:create]`

      def create
        bathroom = Bathroom.new(bathroom_params)
        bathroom.user = current_user
        if bathroom.save
          render json: { status: 'SUCCESS', message: 'Saved new bathroom', bathrooms: bathroom }, status: :ok
        end
      end

      private
      def bathroom_params
        params.require(:bathroom).permit(:establishment, :address, :city, :state, :zip, :gender, :key_needed, :toilet_quantity)
      end

    end
  end
end

现在这正是它应该做的,这很棒。然而,测试......不是那么多。这是我的测试部分:

describe "POST #create" do
  let!(:bath) {{
    establishment: "Fake Place",
    address: "123 Main St",
    city: "Cityton",
    state: "NY",
    zip: "11111",
    gender: "Unisex",
    key_needed: false,
    toilet_quantity: 1
  }}
  let!(:params) { {bathroom: bath} }
  it "receives bathroom data and creates a new bathroom" do
    post :create, params: params

    bathroom = Bathroom.last
    expect(bathroom.establishment).to eq "Fake Place"
  end
end

我确信这里不止一件事有问题,但我很难找到有关测试此方法的正确方法的大量信息。任何见解或建议将不胜感激。

【问题讨论】:

  • 我认为Bathroom.last 很有可能没有返回您期望的记录。
  • 然而,有些人会争辩说这不是控制器测试的东西。您是否有任何验证失败?你能发布测试失败吗?

标签: ruby-on-rails ruby api testing rspec


【解决方案1】:

我会完全跳过控制器规格。 Rails 5 几乎将ActionController::TestCase(RSpec 包装为控制器规范)委托给了垃圾抽屉。控制器测试不会发送真正的 http 请求,也不会删除 Rails 的关键部分,如路由器和中间件。完全折旧和委托给单独的 gem 将很快发生。

相反,您想使用请求规范。

RSpec.describe "API V1 Bathrooms", type: 'request' do
  describe "POST /api/v1/bathrooms" do
    context "with valid parameters" do
      let(:valid_params) do
        {
           bathroom: {
            establishment: "Fake Place",
            address: "123 Main St",
            city: "Cityton",
            state: "NY",
            zip: "11111",
            gender: "Unisex",
            key_needed: false,
            toilet_quantity: 1
          }
        }
      end

      it "creates a new bathroom" do
        expect { post "/api/v1/bathrooms", params: valid_params }.to change(Bathroom, :count).by(+1)
        expect(response).to have_http_status :created
        expect(response.headers['Location']).to eq api_v1_bathroom_url(Bathroom.last)
      end

      it "creates a bathroom with the correct attributes" do
         post "/api/v1/bathrooms", params: valid_params
         expect(Bathroom.last).to have_attributes valid_params[:bathroom]
      end
    end

    context "with invalid parameters" do
       # testing for validation failures is just as important!
       # ...
    end
  end
end

还发送一堆像render json: { status: 'SUCCESS', message: 'Saved new bathroom', bathrooms: bathroom }, status: :ok 这样的垃圾邮件是一种反模式。

作为响应,您应该发送一个 201 CREATED 响应,其位置标头包含指向新创建资源的 url 或包含新创建资源的响应正文。

def create
  bathroom = current_user.bathrooms.new(bathroom_params)
  if bathroom.save
    head :created, location: api_v1_bathroom_url(bathroom)
  else
    head :unprocessable_entity
  end     
end

如果您的客户无法通过查看响应代码来判断响应是否成功,那么您做错了。

【讨论】:

  • 非常感谢您的建议。你写的很有效!
【解决方案2】:

您实际上不需要测试保存在数据库中的记录中的值,您可以执行以下操作:

expect(post :create, params: params).to change(Bathroom, :count).by(1)

这足以测试创建操作是否在所需表上创建了一条记录。

然后您可以添加更多规范来测试浴室.new 是否接收到预期的参数(这样您就知道保存时它会有这些字段),或者存根浴室对象和它的保存方法来测试响应。

如果您想测试保存的记录是否具有正确的值,我认为该规范属于浴室模型而不是控制器(或者更好的是,集成测试)。

【讨论】:

    【解决方案3】:

    所以我听从了 max 的建议,但做了一点改动以使其正常工作。我的最终代码是:

    RSpec.describe "API V1 Bathrooms", type: 'request' do
      describe "POST /api/v1/bathrooms" do
        context "with valid parameters" do
    
          let(:valid_params) do
            {
               bathroom: {
                establishment: "Fake Place",
                address: "123 Main St",
                city: "Cityton",
                state: "NY",
                zip: "11111",
                gender: "Unisex",
                key_needed: false,
                toilet_quantity: 1
              }
            }
          end
    
          it "creates a new bathroom" do
            user = FactoryGirl.create(:user, email: "email1@website.com")
            login_as(user, :scope => :user)
            expect { post "/api/v1/bathrooms", params: valid_params }.to change(Bathroom, :count).by(+1)
            expect(response).to have_http_status :created
            expect(response.headers['Location']).to eq api_v1_bathroom_url(Bathroom.last)
          end
    
          it "creates a bathroom with the correct attributes" do
            user = FactoryGirl.create(:user, email: "email2@website.com")
            login_as(user, :scope => :user)
        post "/api/v1/bathrooms", params: valid_params
            expect(Bathroom.last).to have_attributes valid_params[:bathroom]
          end
        end
      end
    end
    

    关键是使用 FactoryGirl 创建一个新用户,因为浴室需要关联的 user_id 才能有效。

    【讨论】:

    • 使用let(:user){ FactoryGirl.create(:user, email: "email1@website.com") } 而不是重复自己。您还应该将login_as(user, :scope =&gt; :user) 移动到前块。
    • 注意!再次感谢您的帮助!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多