【问题标题】:How to DRY up RSpec tests with redundant `let!` method calling?如何使用冗余的“让!”方法调用来干掉 RSpec 测试?
【发布时间】:2018-02-08 21:01:55
【问题描述】:

我如何在我的 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! 方法调用,但我不知道如何让它 DRY。我不能只定义普通方法,因为在这种情况下,变量将仅在此方法的范围内可用。我也不能让我的类别在一般范围内创建,因为在两种情况下,由于测试性质,它们不应该被创建。那么,技术上我应该怎么做呢?

【问题讨论】:

  • 有什么理由为什么在describe "POST create" do 下的顶级范围内声明它们一次不起作用?
  • 是的,因为在某些情况下我不喜欢在数据库中创建我的类别。
  • 还有更多你没有展示的测试吗?根据您展示的内容,您有三个测试,每个测试都设置相同的测试数据。为什么不在你做出断言之前设置一次呢?
  • 你说的不对。第一组let! 调用在"and if params are valid" 范围内定义,而我们在"and if categories are not valid""and if some common service params are not valid" 范围内没有定义。
  • 调整范围,使前两个范围是“如果参数有效”(这些lets 被声明)与“如果参数无效”(它们不是)和所有其他范围在这些范围内。

标签: ruby-on-rails rspec dry


【解决方案1】:

您可以按如下方式使您的规范干燥:

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

【讨论】:

    【解决方案2】:

    最后我决定将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 - 您建议的调整也适用于那里描述的情况,但在我的应用程序中,我也有更高级的情况,但这还不够。我认为这里提出的解决方案更通用。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-06-07
      • 1970-01-01
      • 1970-01-01
      • 2012-10-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多