2012-01-04 83 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