I have model name: UserApplicationPatient. That model having two associations:
belongs_to :patient
belongs_to :customer
before_create :set_defaults
private
def set_defaults
self.enrol_date_and_time = Time.now.utc
self.pap = '1'
self.flg = '1'
self.patient_num = "hos_#{patient_id}"
end
Factories of UserApplicationPatient
FactoryBot.define do
factory :user_application_patient do
association :patient
association :customer
before(:create) do |user_application_patient, evaluator|
FactoryBot.create(:patient)
FactoryBot.create(:customer)
end
end
end
Model spec:
require 'spec_helper'
describe UserApplicationPatient do
describe "required attributes" do
let!(:user_application_patient) { described_class.create }
it "return an error with all required attributes" do
expect(user_application_patient.errors.messages).to eq(
{ patient: ["must exist"],
customer: ["must exist"]
},
)
end
end
end
This is the first time I am writing specs of models. Could someone please tell me how to write specs for set_defaults before_create methods and factories what I have written is correct or not.
CodePudding user response:
Since you are setting default values in the before_create hook, I will recommend validating it like this
describe UserApplicationPatient do
describe "required attributes" do
let!(:user_application_patient) { described_class.create }
it "return an error with all required attributes" do
# it will validate if your default values are populating when you are creating a new object
expect(user_application_patient.pap).to eq('1')
end
end
end
CodePudding user response:
To test your defaults are set, create a user and test if the defaults are set.
- You definitely don't want to use
let!
, there's no need to create the object until you need it. - Since we're testing
create
there's no use for alet
here at all. - How do we test Time.now? We can freeze time!
- I assume
patient_id
should bepatient.id
.
Here's a first pass.
it 'will set default attributes on create' do
freeze_time do
# Time will be the same everywhere in this block.
uap = create(:user_application_patient)
expect(uap).to have_attributes(
enrol_date_and_time: Time.now.utc,
pap: '1',
flg: '1',
patient_num: "hos_#{uap.patient.id}"
)
end
end
it 'will not override existing attributes' do
uap_attributes = {
enrol_date_and_time: 2.days.ago,
pap: '23',
flg: '42',
patient_num: "hos_1234"
}
uap = create(:user_application_patient, **uap_attributes)
expect(uap).to have_attributes(**uap_attributes)
end
These will probably fail.
- Defaults are set after validation has taken place.
- Existing attributes are overwritten.
- What is
patient_id
?
We can move setting defaults to before validation. That way the object can pass validation, and we can also see the object's attributes before writing it to the database.
We can fix set_defaults so it doesn't override existing attributes.
Time.now
should not be used, it is not aware of time zones. Use Time.current
. And there's no reason to pass in UTC, the database will store times as UTC and Rails will convert for you.
belongs_to :patient
belongs_to :customer
before_validation :set_defaults
private
def set_defaults
self.enrol_date_and_time ||= Time.current
self.pap ||= '1'
self.flg ||= '1'
self.patient_num ||= "hos_#{patient.id}"
end
We can also make your factory a bit more flexible.
FactoryBot.define do
factory :user_application_patient do
association :patient, strategy: :create
association :customer, strategy: :create
end
end
This way, the patient and customer will be created regardless whether you build(:user_application_patient)
or create(:user_application_patient)
. This is necessary for user_application_patient to be able to reference its patient.id.
In general, don't do things at create time.