【问题标题】:Rails 3.1, RSpec: testing model validationsRails 3.1,RSpec:测试模型验证
【发布时间】:2011-11-24 03:01:19
【问题描述】:

我已经开始了我在 Rails 中使用 TDD 的旅程,并且遇到了一个关于模型验证测试的小问题,我似乎无法找到解决方案。假设我有一个用户模型,

class User < ActiveRecord::Base
  validates :username, :presence => true
end

还有一个简单的测试

it "should require a username" do
  User.new(:username => "").should_not be_valid
end

这正确地测试了存在验证,但如果我想要更具体怎么办?例如,在错误对象上测试 full_messages..

it "should require a username" do
  user = User.create(:username => "")
  user.errors[:username].should ~= /can't be blank/
end

我对初始尝试(使用 should_not be_valid)的担忧是 RSpec 不会产生描述性错误消息。它只是说“预期有效?返回假,得到真”。但是,第二个测试示例有一个小缺点:它使用 create 方法而不是 new 方法来获取错误对象。

我希望我的测试更具体地说明他们正在测试什么,但同时不必触及数据库。

有人有意见吗?

【问题讨论】:

    标签: ruby-on-rails ruby-on-rails-3 rspec rspec2 rspec-rails


    【解决方案1】:

    恭喜你努力进入 ROR 的 TDD 我保证一旦你开始你就不会回头。

    最简单的快速而肮脏的解决方案是在每次测试之前生成一个新的有效模型,如下所示:

     before(:each) do
        @user = User.new
        @user.username = "a valid username"
     end
    

    但我建议您为所有模型设置工厂,它们会自动为您生成有效模型,然后您可以处理各个属性并查看您的验证是否正确。我喜欢为此使用FactoryGirl

    基本上,一旦您设置好您的测试,就会如下所示:

    it "should have valid factory" do
        FactoryGirl.build(:user).should be_valid
    end
    
    it "should require a username" do
        FactoryGirl.build(:user, :username => "").should_not be_valid
    end
    

    哦,是的,a good railscast 比我解释得更好:

    祝你好运:)


    更新:从version 3.0 开始,工厂女孩的语法发生了变化。我已经修改了我的示例代码以反映这一点。

    【讨论】:

    • 非常感谢马修。有没有办法更接近我要测试的错误? X.should_not be_valid 对我来说似乎很笼统,谁知道以后的其他事情是否会使记录无效。然后,此测试将在错误的位置失败。顺便说一句,我想我将您的答案标记为已接受。我不是吗?
    • 对,所以这就是我主张工厂的原因。您编写代码以在一个地方一次生成一个有效用户,然后编写一个测试以确保其在所有单个测试之前有效,以确保您可以使其无效。这样,如果由于某种原因您更改了模型,以便工厂不再生产有效用户,Factory.build(:user).should be_valid 测试将失败,您将知道您必须更新您的工厂......明白了吗? (是的,你接受了 my7 的回答)
    • @Feech FactoryGirl.build(:user, username: '').should have(1).errors_on(:username)
    • 对我来说,关键是使用build(或者如果你不使用FactoryGirl,new)而不是create。否则会在测试完成之前引发ActiveRecord::RecordInvalid 异常,导致测试失败。
    • 不要这样测试!见nathanvda's answer below。如果您以这种方式进行测试,那么您实际上是在测试 ActiveRecord 的行为,该行为已经过测试。相反,使用 shoulda-matchers gem 来验证用户是否已进行验证。
    【解决方案2】:

    测试模型验证(以及更多活动记录)的一种更简单的方法是使用像 shouldaremarkable 这样的 gem。

    他们将允许进行如下测试:

    describe User
    
      it { should validate_presence_of :name }
    
    end
    

    【讨论】:

    • 这是很好的检查模型中是否有关联,但请注意它实际上不会尝试创建没有名称的用户并检查其有效性
    • @brafales 实际上没有,afaik 这正是应该做的:它会尝试使用空白名称创建对象,并且应该给出错误。
    【解决方案3】:

    我传统上处理功能或请求规范中的错误内容规范。因此,例如,我有一个类似的规范,我将在下面进行浓缩:

    功能规格示例

    before(:each) { visit_order_path }
    
    scenario 'with invalid (empty) description' , :js => :true do
    
      add_empty_task                                 #this line is defined in my spec_helper
    
      expect(page).to have_content("can't be blank")
    

    然后,我让我的模型规范测试某些东西是否有效,然后我的功能规范测试错误消息的确切输出。仅供参考,这些功能规格需要 Capybara 可以找到 here

    【讨论】:

      【解决方案4】:

      试试这个:

      it "should require a username" do
        user = User.create(:username => "")
        user.valid?
        user.errors.should have_key(:username)
      end
      

      【讨论】:

      • 这是我最喜欢的,很扎实,检查的是key而不是message,这是一个细节
      • 你可以使用 user = User.new(:username => "") 来避免撞到 db
      • @TaufiqMuhammadi new 不会达到数据库级别的验证,例如唯一性索引约束。
      • @mnort9 这个问题专门问不必碰db
      • @TaufiqMuhammadi 很好,我错过了。不过对于那些寻求更完整的验证测试的人来说,这是很好的说明
      【解决方案5】:

      在新版本的 rspec 中,你应该使用 expect 代替 should,否则你会收到警告:

      it "should have valid factory" do
          expect(FactoryGirl.build(:user)).to be_valid
      end
      
      it "should require a username" do
          expect(FactoryGirl.build(:user, :username => "")).not_to be_valid
      end
      

      【讨论】:

      • 您还应该在示例名称中使用现在时动词而不是 should。以上可以改写为"has a valid factory""requires a username"
      【解决方案6】:

      就像@nathanvda 所说,我会利用Thoughtbot 的Shoulda Matchers gem。通过这种方式,您可以按照以下方式编写测试以测试是否存在以及任何自定义错误消息。

      RSpec.describe User do
      
        describe 'User validations' do
          let(:message) { "I pitty da foo who dont enter a name" }
      
          it 'validates presence and message' do
           is_expected.to validate_presence_of(:name).
            with_message message
          end
      
          # shorthand syntax:
          it { is_expected.to validate_presence_of(:name).with_message message }
        end
      
      end
      

      【讨论】:

        【解决方案7】:

        在这里聚会有点晚了,但是如果您不想添加应该匹配器,这应该与 rspec-rails 和 factorybot 一起使用:

        # ./spec/factories/user.rb
        FactoryBot.define do
          factory :user do
            sequence(:username) { |n| "user_#{n}" }
          end
        end
        
        # ./spec/models/user_spec.rb
        describe User, type: :model do
          context 'without a username' do
            let(:user) { create :user, username: nil }
        
            it "should NOT be valid with a username error" do
              expect(user).not_to be_valid
              expect(user.errors).to have_key(:username)
            end
          end
        end
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2017-05-28
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-10-09
          相关资源
          最近更新 更多