【问题标题】:Rails Minitest one model validation causes ArgumentError: You need to supply at least one validationRails Minitest 一种模型验证导致 ArgumentError:您需要提供至少一种验证
【发布时间】:2021-05-11 14:11:32
【问题描述】:

在我的 Rails6 应用程序中,我有两个模型验证,我想通过 Minitest 进行测试:

class Portfolio < ApplicationRecord
  validates :name, :status, presence: true
  validates :initial_return do |record, attr, value|
    record.errors.add(attr, 'Add value between -100 and 100') unless value >= -100 && value <= 100
  end
end

迷你测试:

class PortfolioTest < ActiveSupport::TestCase
  setup do
    @portfolio = Portfolio.create(name: Faker::Bank.name)
  end

  test 'invalid PortfolioSpotlightFigure, does not fit the range (-100, 100)' do
    @portfolio.initial_return = -101
    assert_not @portfolio.valid?
    @portfolio.initial_return = 101
    assert_not @portfolio.valid?
    @portfolio.initial_return = 50
    assert @portfolio.valid?
  end

  context 'validations' do
    should validate_presence_of(:name)
  end
end

Minitest 对这两种情况都给出了相同的错误:

ArgumentError:您需要提供至少一个验证

但是当我从Portfolio 模型中删除:initial_return 字段的验证时:

  validates :initial_return do |record, attr, value|
    record.errors.add(attr, 'Add value between -100 and 100') unless value >= -100 && value <= 100

validate_presence_of(:name) 的测试将通过,这意味着我错误地定义了该验证。我错过了什么?

【问题讨论】:

  • 为什么不干... validates :initial_return, presence: true, inclusion: -100..100
  • @dbugger 错误消息是“初始返回不包含在列表中”,这对用户不是很友好。 validate_numericality_of 提供了更多有用的错误消息。

标签: ruby-on-rails ruby minitest shoulda


【解决方案1】:

你不需要重新发明轮子

class Portfolio < ApplicationRecord
  validates :name, :status, presence: true
  validates :initial_return,
    numericality: {
      greater_than_or_equal_to: -100,
      less_than_or_equal_to: 100
    }
end

并停止在测试中对验证进行地毯式轰炸。测试实际验证,而不是测试整个对象是否有效/无效,这会导致误报和否定。例如:

  test 'invalid PortfolioSpotlightFigure, does not fit the range (-100, 100)' do
    @portfolio.initial_return = -101
    # these will pass even if you comment out the validation on initial_return as 
    # status is nil
    assert_not @portfolio.valid? 
    @portfolio.initial_return = 101
    assert_not @portfolio.valid?
    # Will fail because status is nil
    @portfolio.initial_return = 50
    assert @portfolio.valid?
  end

正如您所见,测试失败不会告诉您模型有效/无效的原因。

每个测试使用一个断言并测试实际验证:

class PortfolioTest < ActiveSupport::TestCase
  setup do
    # you dont need to insert records into the db to test associations
    @portfolio = Portfolio.new
  end

  test 'initial return over 100 is invalid' do
    # arrange
    @portfolio.initial_return = 200
    # act 
    @portfolio.valid?
    # assert
    assert_includes(@portfolio.errors.full_messages, "Initial return must be less than or equal to 100")
  end

  test 'initial return below -100 is invalid' do
    # arrange
    @portfolio.initial_return = -200
    # act 
    @portfolio.valid?
    # assert
    assert_includes(@portfolio.errors.full_messages, "Initial return must be greater than or equal to -100")
  end

  test 'an initial return between -100 and 100 is valid' do
    # arrange
    @portfolio.initial_return = 50
    # act 
    @portfolio.valid?
    # assert
    refute(@portfolio.errors.has_key?(:intial_return))
  end

  # ...
end

应该可以使用validates_numericality_of matcher:

should validate_numericality_of(:initial_return).
            is_greater_than_or_equal_to(-100).
            is_less_than_or_equal_to(100)

【讨论】:

    【解决方案2】:

    预计设置块中的@portfolio = Portfolio.create(name: Faker::Bank.name) 已经失败。

    我不知道它是否会导致实际错误,但是当您不提供初始 initial_return 时,您不能 create 对象。因为它违反了验证本身。

    因为运行数值范围的测试用例,您需要确保您的初始对象是有效的。 这就是为什么当您删除 initial_return 验证时它没有失败的原因,因为 setup 块在没有验证的情况下是成功的。你只是看错了结局。

    所以您要么使用build,它不会将对象持久保存在数据库中,并且最初不会运行验证

    @portfolio = Portfolio.build(name: Faker::Bank.name)
    

    或者如果你想在数据库中持久化对象,你必须确保设置对象是有效的

    @portfolio = Portfolio.create(name: Faker::Bank.name, initial_return: 50)
    

    【讨论】:

    • 这是你不应该使用assert @portfolio.valid? 来测试验证的一个很好的理由。根据您是否满足模型中的所有其他验证,它会给出假阴性或阳性。
    • 嘿,我和你在一起。我只是试图提供一些不同的方法,实际问题是什么以及如何解决它。您的回答虽然非常好,但提供了更多do everything different 方法,我个人并不喜欢这种方法。
    • 是的,很抱歉,如果它看起来很苛刻 - 您确实提出了一个很好的观点,即测试设置本身会将其设置为失败。有时完全改变当然是最好的答案。
    猜你喜欢
    • 1970-01-01
    • 2018-07-11
    • 2012-01-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多