【问题标题】:Is it possible to pass arguments to let block during the call?是否可以在调用期间将参数传递给 let 阻塞?
【发布时间】:2019-09-17 14:35:46
【问题描述】:

我已经开始使用 Rspec 并且现在我编写了几个成功且非常困难的测试。但由于我需要更多练习,所以我对这些测试做了几次重构。

我在 Rspec 的文档中没有找到我的问题的答案,这就是我在这里的原因。

问题是关于指令let,它提供了通过第一次调用返回一些对象的能力,而不仅仅是,你知道的。

我当前的 rspec 代码部分是:

RSpec.describe 'Users', type: :request do
  describe 'profiles' do
    context 'should be visible by' do
      it 'related company managers' do
        company = create(:company)
        sign_in create(:manager, :company, company: company) # Pay attention on this
        get user_path(create(:member, :company, company: company)) # this
        expect(response).to have_http_status 200
      end

      it 'related company owners' do
        company = create(:company)
        sign_in create(:owner, :company, company: company) # this
        get user_path(create(:member, :company, company: company)) # and this
        expect(response).to have_http_status 200
      end
    end
  end
end

在用户规范下只有两个 63 示例,但它们已经足够了。我想重构代码以使用let,它将定义一个带参数的方法,如下所示:

RSpec.describe 'Users', type: :request do
  let(:member) do |entity_name = :company, entity = create(entity_name)|
    create :member, entity_name, entity_name => entity
  end
  let(:manager) do |entity_name = :company, entity = create(entity_name)|
    create :manager, entity_name, entity_name => entity
  end
  let(:owner) do |entity_name = :company, entity = create(entity_name)|
    create :owner, entity_name, entity_name => entity
  end

  describe 'profiles' do
    context 'should be visible by' do
      it 'related company managers' do
        company = create(:company)
        sign_in manager(:company, company) # Become more readable here
        get user_path(member(:company, company)) # here
        expect(response).to have_http_status 200
      end

      it 'related company owners' do
        company = create(:company)
        sign_in owner(:company, company) # here
        get user_path(member(:company, company)) # and here.
        expect(response).to have_http_status 200
      end
    end
  end
end

在重构之后,Guard 说:

  1) Users profiles should be visible by related company managers
     Failure/Error: sign_in manager(:company, company) # Become more readable here

     ArgumentError:
       wrong number of arguments (given 2, expected 0)
     # ./spec/requests/users_spec.rb:38:in `block (4 levels) in <top (required)>'

从我看到的Rspec核心的memorized_helpers.rb

def let(name, &block)
  # We have to pass the block directly to `define_method` to
  # allow it to use method constructs like `super` and `return`.
  raise "#let or #subject called without a block" if block.nil?
  raise(
    "#let or #subject called with a reserved name #initialize"
  ) if :initialize == name
  MemoizedHelpers.module_for(self).__send__(:define_method, name, &block)

  # Apply the memoization. The method has been defined in an ancestor
  # module so we can use `super` here to get the value.
  if block.arity == 1
    define_method(name) { __memoized.fetch_or_store(name) { super(RSpec.current_example, &nil) } }
  else
    define_method(name) { __memoized.fetch_or_store(name) { super(&nil) } }
  end
end

看起来let 的块应该用参数定义。或不? 我没有足够的经验来确定它,我很乐意找到它。

【问题讨论】:

    标签: ruby-on-rails rspec ruby-on-rails-5 rspec3


    【解决方案1】:

    我找到了可能的解决方案:返回一个 lambda 并就地调用它

    RSpec.describe 'Users', type: :request do
      let(:member) do
        ->(entity_name = :company, entity = create(entity_name)) do
          create :member, entity_name, entity_name => entity
        end
      end
      let(:manager) do
        ->(entity_name = :company, entity = create(entity_name)) do
          create :manager, entity_name, entity_name => entity
        end
      end
      let(:owner) do
        ->(entity_name = :company, entity = create(entity_name)) do
          create :owner, entity_name, entity_name => entity
        end
      end
    
      describe 'profiles' do
        context 'should be visible by' do
          it 'related company managers' do
            company = create(:company)
            sign_in manager.(:company, company) # Pay attention on the dot here
            get user_path(member.(:company, company)) # here
            expect(response).to have_http_status 200
          end
    
          it 'related company owners' do
            company = create(:company)
            sign_in owner.(:company, company) # here
            get user_path(member.(:company, company)) # and here.
            expect(response).to have_http_status 200
          end
        end
      end
    end
    

    这不是我想要的,但看起来像。

    【讨论】:

      【解决方案2】:

      我想你想要这样的东西:

      RSpec.describe 'Users', type: :request do
        describe 'profiles' do
          let(:request) { get user_path(user) }
          let(:user)    { create(:member, :company, company: company) }
          let(:company) { create(:company) }
      
          shared_examples_for 'has visibility to user' do
            before { sign_in employee }
      
            it do 
              request
              expect(response).to have_http_status(:success)
            end
          end
      
          context 'manager' do
            let(:employee) { create(:manager, company: company) }
      
            it_behaves_like 'has visibility to user'
          end
      
          context 'owner' do
            let(:employee) { create(:owner, company: company) }
      
            it_behaves_like 'has visibility to user'
          end
        end
      end
      

      同样,您应该能够执行以下操作:

      it_behaves_like 'has visibility to user' do
       let(:employee) { create(:manager, company: company) }
      end
      

      shared examples。我建议查看http://www.betterspecs.org/

      (除此之外,将工厂命名为 :member 而不是 :user 令人困惑,因为 GET 请求是针对 user_path 的;我建议重命名它。)

      【讨论】:

        【解决方案3】:

        另一个解决方案是创建助手。比如在spec/support/users_helper.rb中创建一个文件。

        module UsersSpecHelper
        
          def member(entity = :company)
            create :member, *entity(entity)
          end
        
          def manager(entity = :company)
            create :manager, *entity(entity)
          end
        
          def owner(entity = :company)
            create :owner, *entity(entity)
          end
        
          private
        
            def entity(entity)
              if entity.is_a? Symbol
                [entity, entity => create(entity)]
              else
                name = entity.class.to_s.downcase.to_sym
                [name, name => entity]
              end
            end
        end
        
        RSpec.configure do |config|
          config.include UsersSpecHelper, type: :request
        end
        

        spec/rails_helper.rb 行中取消注释:

        Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
        

        然后它可以更好地使用:

        RSpec.describe 'Users', type: :request do
          describe 'profiles' do
            context 'should be visible by' do
              it 'managers of related company' do
                company = create(:company)
                sign_in manager(company) # Changes are here
                get user_path(member(company)) # here
                expect(response).to have_http_status 200
              end
        
              it 'owners of related company' do
                company = create(:company)
                sign_in owner(company) # here
                get user_path(member(company)) # and here.
                expect(response).to have_http_status 200
              end
            end
          end
        end
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2021-01-20
          • 2021-11-11
          • 1970-01-01
          • 2013-05-07
          • 2011-04-22
          • 1970-01-01
          • 2020-09-14
          • 2012-12-26
          相关资源
          最近更新 更多