如何使用冗余的 `let!` 方法调用来干燥 RSpec 测试?

How to DRY up RSpec tests with redundant `let!` method calling?

我如何在我的 Rails 应用程序中使用 RSpec:

这样的测试块
describe "POST create" do
  describe "if we have a logged in user and he can be an owner" do
    describe "and if params are valid" do
      let!(:service_attributes_with_categories_1_and_2) {
        FactoryBot.attributes_for :service_with_categories_1_and_2
      }
      let!(:category_1) { FactoryBot.create :category, {id: 1} }
      let!(:category_2) { FactoryBot.create :category, {id: 2} }

      it "creates a new service" do
        # ...
      end
      it "creates associations with categories" do
        # ...
      end
    end
    describe "and if categories are not valid" do
      # ...
    end
    describe "and if some common service params are not valid" do
      # ...
    end
  end
  describe "if no user is logged in, but params are valid" do
    let!(:service_attributes_with_categories_1_and_2) {
      FactoryBot.attributes_for :service_with_categories_1_and_2
    }
    let!(:category_1) { FactoryBot.create :category, {id: 1} }
    let!(:category_2) { FactoryBot.create :category, {id: 2} }
    it "doesn't create a new service" do
      # ...
    end
    it "doesn't create associations with categories" do
      # ...
    end
  end
  describe "if logged user cannot be an owner, but params are valid" do
    let!(:service_attributes_with_categories_1_and_2) {
      FactoryBot.attributes_for :service_with_categories_1_and_2
    }
    let!(:category_1) { FactoryBot.create :category, {id: 1} }
    let!(:category_2) { FactoryBot.create :category, {id: 2} }

    it "doesn't create a new service" do
      # ...
    end
    it "doesn't create associations with categories" do
      # ...
    end
  end
end

如我们所见,我有很多冗余的 let! 方法调用,但我不知道如何让它变干。我不能只定义普通方法,因为那样的话,变量将只在这个方法的范围内可用。我也不能让我的类别在一般范围内创建,因为在两种情况下,由于测试性质,它们不应该被创建。那么,我应该如何在技术上做到这一点?

最后我决定将 FactoryBot.create 函数拆分为两个步骤:FactoryBot.build.save 函数 运行 在获得的对象上。它允许我将我的 let! 调用移动到主范围,并定义方法,它在我需要的情况下准确地保存我构建的对象。我的 DRY 代码现在看起来像这样:

describe "POST create" do
  let!(:service_attributes_with_categories_1_and_2) {
    FactoryBot.attributes_for :service_with_categories_1_and_2
  }
  let!(:category_1) { FactoryBot.build :category, {id: 1} }
  let!(:category_2) { FactoryBot.build :category, {id: 2} }

  def save_categories_1_and_2
    category_1.save
    category_2.save
  end

  describe "if we have a logged in user and he can be an owner" do
    describe "and if params are valid" do
      before(:each) do
        save_categories_1_and_2
      end
      it "creates a new service" do
        # ...
      end
      it "creates associations with categories" do
        # ...
      end
    end
    describe "and if categories are not valid" do
      # ...
    end
    describe "and if some common service params are not valid" do
      # ...
    end
  end
  describe "if no user is logged in, but params are valid" do
    before(:each) do
      save_categories_1_and_2
    end
    it "doesn't create a new service" do
      # ...
    end
    it "doesn't create associations with categories" do
      # ...
    end
  end
  describe "if logged user cannot be an owner, but params are valid" do
    before(:each) do
      save_categories_1_and_2
    end
    it "doesn't create a new service" do
      # ...
    end
    it "doesn't create associations with categories" do
      # ...
    end
  end
end

非常感谢@midlins 和@Taryn East 为我指明了正确的轨道。 @Taryn East - 你建议的调整也适用于那里描述的情况,但在我的应用程序中我还有更高级的情况,这还不够。我觉得这里介绍的方案比较通用

您可以将您的规范设为 DRY,如下所示:

1) 使用let而不是定义方法。

2) 安排您的 context 块,以便轻松容纳更多案例。

describe 'POST create' do
  let!(:service_attributes_with_categories_1_and_2) {
    FactoryBot.attributes_for :service_with_categories_1_and_2
  }
  let!(:category_1) { FactoryBot.build :category, {id: 1} }
  let!(:category_2) { FactoryBot.build :category, {id: 2} }
  let(:save_categories_1_and_2) { category_1.save && category_2.save }

  context 'when user is logged in' do
    context 'when user is an owner' do
      context 'when params are valid' do
        before do
          save_categories_1_and_2
        end

        it 'creates a new service' do
        end

        it 'creates associations with categories' do
        end
      end

      context 'when categories are not valid' do
      end

      context 'when some common service params are not valid' do
      end
    end

    context 'when user is not an owner' do
      context 'when params are valid' do
        before do
          save_categories_1_and_2
        end

        it "doesn't create a new service" do
        end

        it "doesn't create associations with categories" do
        end
      end
    end
  end

  context 'when no user is logged in' do
    context 'when params are valid' do
      before do
        save_categories_1_and_2
      end

      it "doesn't create a new service" do
      end

      it "doesn't create associations with categories" do
      end
    end
  end
end