rails test models 出現 ActiveRecord::NotNullViolation 的問題

最近在玩 rails 內建的測試(沒意外的話,等我玩完會再寫一篇筆記),剛開始看到 models 測試的範例時,我發現和 rspec 中 model 的測試內容很像,所以便決定從這邊開始……然後在做了幾個簡單的 user 和 profile 的功能後,我愕然發現,跑內建的測試(如下)它跳了 error 啊!

test "the truth" do
    assert true
  end
end

我們來看看錯誤訊息:
Error:
UserTest#test_the_truth:
ActiveRecord::NotNullViolation: PG::NotNullViolation: ERROR:  null value in column "user_id" of relation "profiles" violates not-null constraint
DETAIL:  Failing row contains (980190962, null, , , null, , 2023-01-17 17:22:32.295653, 2023-01-17 17:22:32.295653).

……看起來像資料庫那邊出了什麼錯? 不對啊,我從頭到尾就沒寫過半行跟資料庫有關的測試。 稍微把錯誤訊息拿去搜尋了一下(老實說,這邊我花了一點時間去過濾資料),最後我找到了這篇討論

我忘記我是不是很快就注意到它了,但我鐵定有忽略掉它一次——因為當時我腦袋裡想到的 fixture,是在 PHPUnit 裡碰過的那個,所以下意識地認為它和 Unit Test、Integrated Testing 一樣就是一種測試類型。

但在仔細看完以後我發現我錯了,從文件中可以發現,rails 中的 fixture 指的,比較貼近於……測試用資料。

好,接下來就來記錄解法吧。


fixtures :all 設為註解

去 test/test_helper.rb,把下面這行註解掉即可

fixtures :all

這是上面討論提到的方法,簡單來說就是不使用 fixture 了。這確實可以幫助我們讓上面的測試跑起來,但這基本上只是治標不治本,不是很推薦。

 

設定好 fixture 的檔案

其實也不難,就是按照文件去設定。如果使用者是用 devise 的話,encrypted_password 可以這樣寫:

testuser:
  id: 1
  email: testuser@test.com
  encrypted_password: <%= Devise::Encryptor.digest(User, 'testpasswd') %>

(P.S. 撰寫此文時,已知可用的 rails 版本範圍是 3.5.1~7.0.4)

本以為在 test/fixtures/users.yml 設定了 id、email 和 encrypted_password 就可以了,沒想到測試一跑下去……

Error:
UserTest#test_the_truth:
ActiveRecord::NotNullViolation: PG::NotNullViolation: ERROR:  null value in column "user_id" of relation "profiles" violates not-null constraint
DETAIL:  Failing row contains (980190962, null, , , null, , 2023-01-17 17:22:32.295653, 2023-01-17 17:22:32.295653).

怎麼還是你?

剛剛的 fixtures :all 註解掉了以後就什麼都能跑了,沒意外的話就是我的 fixture 寫錯,可我也就寫了那麼四行,參考別人的寫法看了半天怎麼想也不是這邊寫錯。

後來我想到,我這不是在寫 models test 嗎?去看看我 model 裡面都寫了什麼好了,接著我在 app/models/user.rb 裡看到了這段:

  after_create :init_profile
  def init_profile
    self.create_profile!
  end

對喔,我建立 user 時會一起建立 profile 欸。

於是我在 test/fixtures/profiles.yml 中加入:

testuser:
  user_id: 1

接著就可以順利跑測試啦!

 

 

一點點,算是後記的小東西
(主要是為了怕未來的我看了上面的內容後,覺得現在的我怎麼會這麼做事而寫的) 

嗯,我知道,先寫程式再寫測試這種事一點都不符合 TDD 的精神。明明一開始就打算要寫看看 rails 的測試了,卻先寫了程式實在不是什麼明智之舉。

再說了,如果最開始就先測試再程式,根本不會因為 fixture 設定時沒有考慮到程式動作而卡關這麼久。但停下來仔細想想啊,作為工程師,其實我們接手別人留下來的專案的可能性,總是遠遠大於自己從 0 開始。

如果想在繼承到的專案上導入自動化測試……我想,遲早會遇到上面那種情況,到時總不能因為忘了又要花時間重新研究吧。




留言