正如标题所暗示的那样,我只是尝试使用 RSpec 在我的 API 控制器中测试创建操作。控制器如下所示:
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
我确定这里有不止一件事是错误的,但我很难找到有关测试此方法的正确方法的大量信息。任何见解或建议将不胜感激。
我会完全跳过控制器规格。Rails 5 几乎将ActionController::TestCase
(RSpec 包装为控制器规格)委托给垃圾抽屉。控制器测试不会发送真正的 http 请求,也不会存根 Rails 的关键部分,如路由器和中间件。总折旧和委托给单独的宝石将很快发生。
相反,您希望使用请求规范。
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
如果您的客户无法通过查看响应代码来判断响应是否成功,那么您做错了。
您实际上不需要测试保存在数据库中的记录中的值,您可以执行以下操作:
expect(post :create, params: params).to change(Bathroom, :count).by(1)
这足以测试创建操作是否在所需表上创建记录。
然后,您可以添加更多规范来测试 Bathroom.new 是否接收预期的参数(这样您就知道它在保存时会有这些字段),或者存根浴室对象,它是 save 方法来测试响应。
如果您想测试保存的记录是否具有正确的值,我认为该规范属于 Bath 模型而不是控制器(或者更好的是集成测试)。
所以我遵循了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才能有效。