Rails 3.1,RSpec:测试模型验证

Posted

技术标签:

【中文标题】Rails 3.1,RSpec:测试模型验证【英文标题】:Rails 3.1, RSpec: testing model validations 【发布时间】: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 方法来获取错误对象。

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

有人有意见吗?

【问题讨论】:

【参考方案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】:

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

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

describe User

  it  should validate_presence_of :name 

end

【讨论】:

这是很好的检查模型中是否有关联,但请注意它实际上不会尝试创建没有名称的用户并检查其有效性 @brafales 实际上没有,afaik 这正是应该做的:它会尝试使用空白名称创建对象,并且应该给出错误。 你是对的,好像我读错了代码github.com/thoughtbot/shoulda-matchers/blob/master/lib/shoulda/…【参考方案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

【讨论】:

以上是关于Rails 3.1,RSpec:测试模型验证的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 RSpec 在 Rails 中测试:包含验证

Rails Rspec 单元测试 验证接口返回格式

在 Rails 3.1 中使用 Capybara、Rspec 和 Selenium 进行测试时登录失败

使用 Rspec 测试 Rails 3.1 可安装引擎

Proc中的RSpec模型验证测试错误

Rails 3.1 插件 gem、虚拟测试应用程序、rspec