2012-01-04 84 views
4

Michael Hartl在 Ruby on Rails 3教程的第6.2.4节中描述了一个关于检查电子邮件地址唯一性的警告:如果两个相同的请求及时接近,则请求A可以通过验证,然后B通过验证,然后A得到保存,然后B得到保存,并获得两个具有相同值的记录。每次检查时都是有效的。Railstutorial.org验证唯一的电子邮件

我的问题是不是关于解决方案(在数据库上放一个唯一的约束,所以B的保存将不起作用)。这是关于写一个测试来证明解决方案的工作原理。我尝试写我自己的,但无论我想出什么,只是模仿常规,简单的唯一性测试。

完全被新rspec的,我天真的做法是只写场景:

it 'should reject duplicate email addresses with caveat' do 
    A = User.new(@attr) 
    A.should be_valid   # always valid 

    B = User.new(@attr) 
    B.should be_valid   # always valid, as expected 

    A.save.should == true  # save always works fine 

    B.save.should == false  # this is the problem case 
    # B.should_not be_valid # ...same results as "save.should" 
end 

但这种测试通过/失败完全相同的情况下,作为常规唯一性检查;我的代码写入时会通过B.save.should == false,以便常规唯一性测试通过,并且在常规测试失败时失败。

所以我的问题是“我怎么写一个rspec测试来验证我正在解决这个问题?”如果答案结果是“这很复杂”,我应该看看是否有不同的Rails测试框架?

+0

这是一个很好的第一个问题BTW。 – 2012-01-04 06:24:00

回答

4

这很复杂。种族条件如此恶劣,正是因为它们很难复制。在内部,save是这样的:

  1. 验证。
  2. 写入数据库。

所以,重现了计时问题,你需要安排两个save调用这样重叠(伪Rails的):

a.validate # first half of a.save 
b.validate # first half of b.save 
a.write_to_db # second half of a.save 
b.write_to_db # second half of b.save 

,但你不能打通save方法,并很容易地摆弄它的内部。

但(这是一个很大的,但是),you can skip the validations entirely

注意save也有如果通过:validate => false作为参数跳过验证的能力。应谨慎使用此技术。

所以如果你使用

b.save(:validate => false) 

你应该得到的只是“写入到数据库” bsave的一半,您的数据发送到数据库,而验证。这应该触发约束冲突在数据库中,我敢肯定,这将引发一个ActiveRecord::StatementInvalid例外,所以我认为你需要寻找一个例外,而不是从save只是一个虚假的回报:

b.save(:validate => false).should raise_exception(ActiveRecord::StatementInvalid) 

你可以收紧以查找特定的异常消息。我没有任何东西可以方便地测试这个测试,所以在Rails控制台中尝试一下并适当调整你的规格。

+0

可爱。谢谢。我花了几次尝试来检查,因为......我实际上做了唯一性错误,并且我的索引是**而不是**,实际上是唯一的。控制台的实际错误是'ActiveRecord :: RecordNotUnique:SQLite3 :: SQLException:列电子邮件不是唯一的:...'。最后,我需要'lambda'来检查异常事件:'lambda {B.save(:validate => false)} .should raise_exception(ActiveRecord :: RecordNotUnique)' – mcdave 2012-01-11 05:07:26

+0

@mcdave:酷,测试应该找到错误:) – 2012-01-11 05:28:48