如何使用 Rspec 在 FactoryBot 生成的模型上测试 "Model.valid?"?

How to test "Model.valid?" on a FactoryBot generated model using Rspec?

我创建了一个聊天室模型,它首先验证某些字段的存在,因此在控制器中我创建了聊天室,然后使用 .valid? 方法确定响应来检查它是否有效。现在,当我使用 FactoryBot 创建测试模型时,测试不会通过 if 语句,它会 returns 响应,就好像测试已经完成一样。

我的操作代码

def create
    new_chatroom = Chatroom.create(chatroom_params)
    if new_chatroom.valid?
      new_chatroom.members.create({ user_id: @current_user[:username] })
      render_response(new_chatroom, :created)
    else
      render_error_response(new_chatroom.errors, :bad_request)
    end
  end

工厂代码

FactoryBot.define do

  factory :chatroom do
    topic { Faker::Lorem.unique.question }
    slug { Faker::IndustrySegments.unique.sub_sector }
    description { Faker::Lorem.paragraph }
    owner { Faker::Name.first_name }
    public { true }
  end
end

这是我的测试

it "creates a new chatroom" do
      post :create, params: {
        :topic => "test chatroom",
          :slug => "code-testing",
          :description => "Testing with Rspec",
      }
      expect(response).to have_http_status(:created)
    end

这里是 render_response 方法:

def render_response(resource, status)
    if block_given?
      yield(resource, status)
    else
      render json: resource, :status => status
    end
  end

测试失败:

Failure/Error: expect(response).to have_http_status(:created)
       expected the response to have status code :created (201) but it was :ok (200)

我遇到了这个失败,当我试图让它通过时(误报),覆盖范围显示我正在测试的响应并不是我实际操作的响应,因为其余行从 if 语句不被覆盖。

but I thought FactoryBot takes over the whole model creation in the tests.

否 - FactoryBot 仅提供创建模型实例的工厂。这被广泛用作在测试前填充数据库的固定装置的替代品。与灯具不同,这不是自动的。

除了生成器将创建工厂文件这一事实之外,仅添加 FactoryBot 对您的应用程序绝对没有任何改变。它不会以任何方式影响模型的行为。

在测试资源创建时,您需要测试:

  1. 给定有效参数,然后模型应该持久保存到数据库
  2. 给定有效参数,则响应应该成功并指向新创建的资源。
  3. 给定无效参数,则不应将模型持久保存到数据库
  4. 给定无效参数,则响应应为 422 并呈现错误页面。

您想使用请求规范而不是控制器规范来测试它。

Request specs provide a high-level alternative to controller specs. In fact, as of RSpec 3.5, both the Rails and RSpec teams discourage directly testing controllers in favor of functional tests like request specs.

require "rails_helper"

RSpec.describe "Chatroom creation", type: :request do

 let(:valid_params) do
   {
     chatroom: {
       topic: "test chatroom",
       slug: "code-testing",
       description: "Testing with Rspec"
     }
   }
  end

  let(:invalid_params) do
   {
     chatroom: {
       topic: ''
     }
   }
  end

  context "when the parameters are valid" do
    it "creates a new chatroom" do
      expect do
        post '/chatrooms', params: valid_params
      end.to change(Chatroom, :count).by(1)
    end

    it "returns success" do
      post '/chatrooms', params: valid_params
      expect(response).to have_http_status(:created)
    end
  end 

  context "when the parameters are invalid" do
    it "does not create a new chatroom" do
      expect do
        post '/chatrooms', params: invalid_params
      end.to_not change(Chatroom, :count)
    end

    it "returns bad entity" do
      post '/chatrooms', params: invalid_params
      expect(response).to have_http_status(:unprocessable_entity)
    end
  end
end

然后我们可以解决您的控制器的问题,它应该是:

class ChatroomsController < ApplicationController
  # ...
  def create
    new_chatroom = Chatroom.new(chatroom_params)
    if new_chatroom.save
      new_chatroom.members.create(user_id: @current_user[:username])
      render_response(new_chatroom, :created)
    else
      render_error_response(new_chatroom.errors, :bad_request)
    end
  end
end

您永远不应使用 .valid? 检查记录是否已保存到数据库中。它仅确保模型级别验证已通过。并不是来自 .create 的 INSERT 语句实际上在数据库中创建了一行。有关可能发生的情况的示例,请参阅 The Perils of Uniqueness Validations

虽然您可以使用 new_chatroom.persisted?,但这是常见的 rails 惯用语,因为它为您提供了一个变量,您可以在记录持久化之前对其进行操作。

class ChatroomsController < ApplicationController
  # ...
  def create
    new_chatroom = Chatroom.new(chatroom_params)
    new_chatroom.members.new(user: current_user)
    if new_chatroom.save
      render_response(new_chatroom, :created)
    else
      render_error_response(new_chatroom.errors, :bad_request)
    end
  end
end

我不会尝试在控制器规范中测试模型有效性,而是编写请求规范(如其他受访者所建议的)。您可以像这样在模型本身上测试对象的有效性:

context 'valid' do
  let(:chatroom) { create :chatroom }

  it { expect(chatroom).to be_valid }
end

context 'fails validation' do
  let(:chatroom) { create :chatroom, topic: nil }

  it { expect(chatroom).not_to be_valid }
end

资源:https://www.rubydoc.info/gems/rspec-rails/RSpec%2FRails%2FMatchers:be_valid

但是如果你想检查实际字段是否经过验证,我建议在模型上使用 shoulda 匹配器,如下所示:

it { should validate_presence_of :topic }
it { should validate_presence_of :slug }

资源:https://github.com/thoughtbot/shoulda-matchers