Rails test using rspec, shoulda_matcher and factory_bot_rails rturns PG::NotNullViolation: ERROR: null value in column

715 views Asked by At

I have 2 model: Answer and Book.

Although they are very similar, all tests pass to Answer but 1 test fails for Book

models/answer.rb

class Answer < ApplicationRecord
  belongs_to :question, class_name: "Question", foreign_key: "question_id"

  validates :text, presence: true
  validates :correct, presence: true
  validates :question_id, presence: true
end

models/book.rb

class Book < ApplicationRecord
  belongs_to :grade, class_name: "Grade", foreign_key: "grade_id"

  validates :name, presence: true, uniqueness: { case_sensitive: false }
  validates :grade_id, presence: true  
end

spec/factories/answers.rb

FactoryBot.define do
  factory :answer do
    text { Faker::Book.author }
    correct { %i(false, true).sample }
    question
  end
end

spec/factories/books.rb

FactoryBot.define do
  factory :book do
    name { Faker::Book.title }
    grade
  end
end

spect/models/answer_spec.rb

require 'rails_helper'

RSpec.describe Answer, type: :model do

  describe 'validation' do
    it { should validate_presence_of(:text) }
    it { should validate_presence_of(:correct) }
    it { should validate_presence_of(:question_id) }
    it { should belong_to :question }
  end
end

spect/models/book_spec.rb

require 'rails_helper'

RSpec.describe Book, type: :model do

  describe 'validation' do
    it { should validate_presence_of(:name) }
    it { should validate_uniqueness_of(:name).case_insensitive }
    it { should validate_presence_of(:grade_id) }
    it { should belong_to :grade }
  end
end

The message I get in return is:

  1. Book validation is expected to validate that :name is case-insensitively unique Failure/Error: it { should validate_uniqueness_of(:name).case_insensitive }

    Shoulda::Matchers::ActiveRecord::ValidateUniquenessOfMatcher::ExistingRecordInvalid: validate_uniqueness_of works by matching a new record against an existing record. If there is no existing record, it will create one using the record you provide.

    While doing this, the following error was raised:
    
      **PG::NotNullViolation: ERROR:  null value in column "grade_id" violates not-null constraint**
      DETAIL:  Failing row contains (2, null, null, 2021-12-23 15:03:39.977355, 2021-12-23 15:03:39.977355).
    
    The best way to fix this is to provide the matcher with a record where
    any required attributes are filled in with valid values beforehand.
    

I have tried many ways that I came up with or saw in stack/git posts but none seams to work.

2

There are 2 answers

2
Schwern On

This situation is covered in the validates_uniqueness_of docs.

If there is no existing record, it will create one using the record you provide.

Your spec doesn't say how to create a Book, and it doesn't know that it needs a Grade. You need to tell it, it won't use the factory. Do this by adding a subject for your one-liners to use.

require 'rails_helper'

RSpec.describe Book, type: :model do
  subject { build(:book) }

  describe 'validation' do
    it { should validate_presence_of(:name) }
    it { should validate_uniqueness_of(:name).case_insensitive }
    it { should belong_to :grade }
  end
end

Note that it { should validate_presence_of(:grade_id) } is redundant with it { should belong_to :grade }. belongs_to already validates presence.

0
Wagner Braga On

When trying test uniqueness in a model that has a reference you need to provide one to be compare first.

For doing so you should specify the subject:

 describe 'validation' do
    subject{ 
    }

After that you can call FactoryBot to build the one to be compere to:

FactoryBot.build(:book)

Final snipet:

  describe 'validation' do
    subject{ 
     FactoryBot.build(:book)
    }

Mr. [Schwern][1] Enhanced the code a little better:

describe 'validation' do
  subject{ build(:book)}