【问题标题】:What is an equivalent to database_cleaner in Rails 6?Rails 6 中的 database_cleaner 等价物是什么?
【发布时间】:2020-03-14 12:06:10
【问题描述】:

我有一个规范,有一个对象和两个上下文。在一种情况下,我将一个键设置为 nil,而在另一种情况下则不设置:

describe SomeClass::SomeService, type: :model do
  describe '#some_method' do

    subject { described_class.new(params, current_user).some_method }
    mocked_params = {
      min_price: 0,
      max_price: 100
    }

    let(:params) { mocked_params }
    let(:current_user) { User.create(email: 'name@mail.com') }

    context 'with invalid params' do
      it 'returns nil if any param is nil' do
        params[:min_price] = nil
        expect(subject).to eq(nil)
      end
    end

    context 'with valid params' do
      it 'returns filtered objects' do
        expect(subject).to eq([])
      end
    end
  end
end

问题是第二次测试失败,因为min_price 仍然为零。

  1. 我从 Rails 5 读到我不需要database_cleaner。我需要还是不需要?
  2. 我认为let 方法每次看到变量时都会创建一个新对象。由于我有两个上下文,并且在这两个上下文中都调用了subject 方法,并且在subject 内部我有变量params,为什么params 对象不是一个新的对象,其中所有字段都在每个上下文?

【问题讨论】:

    标签: ruby-on-rails ruby rspec


    【解决方案1】:

    我从 Rails 5 中读到我不需要 database_cleaner。我需要 还是不行?

    没有。它不再需要了。在以前的 Rails 版本中,回滚更改的数据库事务方法仅(有时)与 TestUnit/Minitest 和夹具一起使用。

    我以为let方法每次看到都会创建一个新对象 变量。由于我有两个上下文,并且主题方法是 在他们两个中调用,并且在主题内部我有变量 params,为什么 params 对象在每个上下文中都不是一个新对象? (和 所有字段)

    这是完全错误的。

    使用 let 定义一个记忆化的辅助方法。该值将被缓存 跨同一示例中的多个调用,但不跨示例。

    当你这样做时:

    mocked_params = {
      min_price: 0,
      max_price: 100
    }
    
    let(:params) { mocked_params }
    

    您实际上只是返回对对象 mocked_params 的引用,然后改变该对象。

    如果你这样做:

    let(:params) do 
     {
        min_price: 0,
        max_price: 100
      }
    end
    

    您将在第一次调用let 时获得一个新的哈希对象,然后该值将被缓存但不会在示例之间共享。但这只是这个规范的冰山一角。

    describe SomeClass::SomeService, type: :model do
      describe '#some_method' do
        let(:current_user) { User.create(email: 'name@mail.com') }
        # explicit use of subject is a serious code smell!
        let(:service) { described_class.new(params, current_user) }
    
        context 'with invalid params' do
          # since let is lazy loading we can define it in this context instead
          let(:params) do
            {
              min_price: nil,
              max_price: 100
            }
          end
    
          it 'returns nil if any param is nil' do
            # actually call the method under test instead of misusing subject
            # this makes it much clearer to readers what you are actually testing
            expect(service.some_method).to eq(nil)
          end
        end
    
        context 'with valid params' do
          let(:params) do
            {
              min_price: 0,
              max_price: 100
            }
          end
    
          it 'returns filtered objects' do
            expect(service.some_method).to eq([])
          end
        end
      end
    end
    

    为什么被测对象将哈希作为第一个位置参数而不是作为 Ruby 方式的最后一个参数,这也很值得怀疑。

    【讨论】:

    • 感谢所有理论输入:'# 明确使用主题是严重的代码异味!'为什么我对主题的使用如此错误?
    • 因为测试的可读性受到严重影响。请参阅blog.davidchelimsky.net/blog/2012/05/13/… - 是的,它很旧,代码已经过时,但总体观点仍然存在。
    • 你觉得let(:some_method)怎么样,这样我就不用多次使用expect(service.some_method)了?
    • 它是否提高了规范的可读性?不,它会显着干燥吗?没有。
    • 当然这取决于具体的用例。是否在每个规范中使用略有不同的参数调用被测方法? - 不。它是否被重复调用相同的参数并且参数很长? - 是的。是否值得增加复杂性?我不知道。
    【解决方案2】:

    发生这种情况是因为您仅在文件加载时初始化 mocked_params 一次,然后您在第一次测试中更改了该哈希。

    而是在 let 块中创建参数,这将导致为每个测试重新创建哈希。

    改变

    mocked_params = {
      min_price: 0,
      max_price: 100
    }
    
    let(:params) { mocked_params }
    

    let(:params) do
      {
        min_price: 0,
        max_price: 100
      }
    end
    

    【讨论】:

    • 我了解问题和解决方案,但是如果我想在 let 声明之外使用 mocked_pa​​rams 怎么办,只是为了有一个干净的代码。不可能吗?
    • 还有什么可能,但是您需要在使用let(:params) { mocked_params.dup } 之前复制或克隆哈希。顺便说一句,我认为您的版本更冗长,因此不干净。
    猜你喜欢
    • 2010-10-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-02-15
    • 1970-01-01
    • 2014-05-08
    • 2014-06-12
    相关资源
    最近更新 更多