Rails 3.1,RSpec:测试模型验证


71

我从在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方法来获取错误对象。

我希望我的测试可以更具体地说明他们正在测试的内容,但同时不必接触数据库。

有人有意见吗?

Answers:


96

恭喜您使用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

噢,这是一个很好的栏杆,比我更好地解释了这一切:

祝好运 :)


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


2
非常感谢Matthew。有没有办法更接近我要测试的错误?X.should_not be_valid在我看来是如此通用,谁知道将来是否有其他事情会使记录无效。然后,该测试将在错误的位置失败。顺便说一句,我想我已将您的回答标记为已接受。不是吗
Feech

7
是的,所以这就是为什么我要求工厂。您编写代码以一次在一个地方产生一个有效的用户,然后编写一个测试以确保其有效,然后再进行所有单独的测试以确保您可以使它无效。这样,如果出于某种原因您更改了型号,以便更长的工厂产生一个有效的用户,Factory.build(:user).should be_valid测试将失败,并且您将知道必须更新工厂...知道吗?(是的,您接受了my7的答案)
Matthew

@Feech FactoryGirl.build(:user,username:'')。应该有(1).errors_on(:username)
Jinyoung Kim

对我来说,关键是使用build(或者,如果您没有使用FactoryGirl new),而不是create。否则ActiveRecord::RecordInvalid,在测试完成之前会引发异常,从而导致失败。
LouieGeetoo 2014年

不要这样测试!请参阅下面的nathanvda的答案。如果以这种方式进行测试,则实际上是在测试ActiveRecord的行为,该行为已经过测试。而是使用Shoulda-matchers gem来验证用户是否具有适当的验证。
ybakos

44

测试模型验证(以及大量活动记录)的一种简单方法是使用诸如Shoulda非凡的gem 。

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

describe User

  it { should validate_presence_of :name }

end

1
最好检查一下模型中是否具有关联,但是要知道,它实际上不会尝试创建没有名称的用户并检查其有效性
brafales 2012年

3
@brafales实际上没有,afaik确实应该执行此操作:它将尝试创建具有空白名称的对象,并且应该给出错误。
nathanvda 2012年

2

16

尝试这个:

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

这是我的最爱,非常扎实,要检查密钥而不是消息,这是一个细节
生态版,2015年

4
您可以只使用user = User.new(:username =>“”)来避免进入数据库
Taufiq Muhammadi

@TaufiqMuhammadinew将不会进行数据库级别的验证,例如唯一性索引约束。
mnort9 '17

@ mnort9这个问题专门问不必接触数据库
Taufiq Muhammadi

@TaufiqMuhammadi很好,我错过了。值得一提的是,对于那些寻求更完整的验证测试的人
mnort9 '17

4

在新版本的rspec中,您应该使用Expect代替,否则,您将得到警告:

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

您还应该使用现在时动词,而不要在示例名称中使用。上面可以重写为"has a valid factory""requires a username"
BrunoFacca '17

0

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

功能规格示例

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,可以在此处找到。


0

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

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

0

在这里参加聚会有点晚,但是如果您不想添加Shoulda Matchers,则应该与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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.