[英]Rails Rspec - How to set polymorphic has_many association
I have a model (payment) that belongs to another model (event), via a polymorphic association.我有一个 model(付款),它属于另一个 model(事件),通过多态关联。
Some tests are failing because the owner model (event) is accessed by the payment model in validations, but the event is returning nil.某些测试失败,因为所有者 model 在验证中访问了所有者 model(事件),但事件返回 nil。 All the features work fine when testing app directly in the browser.
直接在浏览器中测试应用程序时,所有功能都可以正常工作。 I added some more comments to
payment.rb
below.我在下面的
payment.rb
中添加了更多评论。
I've tried defining the association in the factories, but no luck.我已经尝试在工厂中定义关联,但没有运气。
What is the best way to set up this association in the spec?在规范中设置此关联的最佳方式是什么?
# models/event.rb
class Event < ApplicationRecord
has_many :payments, as: :payable, dependent: :destroy
end
# models/payment.rb
class Payment < ApplicationRecord
belongs_to :payable, polymorphic: true
validate :amount_is_valid
def amount_is_valid
if amount.to_i > payable.balance.to_i
errors.add(:amount, "can't be higher than balance")
end
end
end
Both examples in this spec are failing.本规范中的两个示例都失败了。
# spec/models/payment_spec.rb
require 'rails_helper'
RSpec.describe Payment, type: :model do
let!(:event) { FactoryBot.create(:event, event_type: 'test', total: 10000, balance: 10000) }
let!(:user) {FactoryBot.create(:user)}
let!(:payment) {
FactoryBot.build(:payment,
amount: 300,
method: 'cash',
payer_id: user.id,
payable_id: event.id,
status: 1,
)
}
describe 'Association' do
it do
# This will fail with or without this line
payment.payable = event
is_expected.to belong_to(:payable)
end
end
# Validation
describe 'Validation' do
describe '#amount_is_valid' do
it 'not charge more than event balance' do
# This will make the test pass. The actual spec has a lot more examples though,
# would rather just set the association once.
# payment.payable = event
payment.amount = 5000000
payment.validate
expect(payment.errors[:amount]).to include("can't be higher than balance")
end
end
end
end
Output Output
# bundle exec rspec spec/models/payment_spec.rb
Randomized with seed 42748
Payment
Association
should belong to payable required: true (FAILED - 1)
Validation
#amount_is_valid
not charge more than event balance (FAILED - 2)
Failures:
1) Payment Association should belong to payable required: true
Failure/Error: if amount.to_i > payable.balance.to_i
NoMethodError:
undefined method `balance' for nil:NilClass
# ./app/models/payment.rb:9:in `amount_is_valid'
# ./spec/models/payment_spec.rb:23:in `block (3 levels) in <top (required)>'
# ./spec/rails_helper.rb:80:in `block (3 levels) in <top (required)>'
# ./spec/rails_helper.rb:79:in `block (2 levels) in <top (required)>'
# ./spec/spec_helper.rb:108:in `block (2 levels) in <top (required)>'
2) Payment Validation #amount_is_valid not charge more than event balance
Failure/Error: if amount.to_i > payable.balance.to_i
NoMethodError:
undefined method `balance' for nil:NilClass
# ./app/models/payment.rb:9:in `amount_is_valid'
# ./spec/models/payment_spec.rb:39:in `block (4 levels) in <top (required)>'
# ./spec/rails_helper.rb:80:in `block (3 levels) in <top (required)>'
# ./spec/rails_helper.rb:79:in `block (2 levels) in <top (required)>'
# ./spec/spec_helper.rb:108:in `block (2 levels) in <top (required)>'
Top 2 slowest examples (0.29972 seconds, 71.6% of total time):
Payment Association should belong to payable required: true
0.28796 seconds ./spec/models/payment_spec.rb:18
Payment Validation #amount_is_valid not charge more than event balance
0.01176 seconds ./spec/models/payment_spec.rb:32
Finished in 0.4186 seconds (files took 4.31 seconds to load)
2 examples, 2 failures
Failed examples:
rspec ./spec/models/payment_spec.rb:18 # Payment Association should belong to payable required: true
rspec ./spec/models/payment_spec.rb:32 # Payment Validation #amount_is_valid not charge more than event balance
Passing specs based on Schwern's feedback.根据 Schwern 的反馈通过规范。 Still using a custom validation for amount, because
balance
is a field on the associated payable
, not the payment
(couldn't find a way to access an associated model from inside a built-in validation helper)仍在对金额使用自定义验证,因为
balance
是关联的payable
字段,而不是payment
(无法找到从内置验证助手内部访问关联 model 的方法)
# payment.rb
class Payment < ApplicationRecord
belongs_to :payable, polymorphic: true
validates :payable, presence: true
validate :amount_is_valid
def amount_is_valid
if amount > payable.balance
errors.add(:amount, "can't be greater than balance")
end
end
end
# spec/models/payment_spec.rb
require 'rails_helper'
RSpec.describe Payment, type: :model do
let(:event) { FactoryBot.create(:event, event_type: 'test', total: 10000, balance: 10000) }
let(:user) {FactoryBot.create(:user)}
let(:payment) {
FactoryBot.build(:payment,
amount: 300,
method: 'cash',
payer_id: user.id,
payable: event,
status: 1,
)
}
describe '#payable' do
it 'is an Event' do
expect(payment.payable).to be_a(Event)
end
end
describe '#amount' do
context 'amount is higher than balance' do
before {
payment.amount = payment.payable.balance + 1
}
it 'is invalid' do
payment.validate
expect(payment.errors[:amount]).to include("can't be greater than balance")
end
end
end
end
Your first test is not failing where you think it is.您的第一个测试并没有在您认为的地方失败。 It's failing on the next line,
is_expected.to belong_to(:payable)
.它在下一行
is_expected.to belong_to(:payable)
上失败了。
You're setting payment
, but you're testing the implicitly defined subject which will be Payment.new
.您正在设置
payment
,但您正在测试隐式定义的主题Payment.new
。
is_expected.to belong_to(:payable)
Is equivalent to...相当于...
expect(subject).to belong_to(:payable)
And since you have no defined subject
this is...而且由于您没有定义的
subject
,因此...
expect(Payment.new).to belong_to(:payable)
Payment.new
does not have payable
defined and so the amount_is_valid
validation errors. Payment.new
没有定义payable
,因此amount_is_valid
验证错误。
To fix this, test payment
directly.要解决此问题,请直接测试
payment
。 And I would suggest staying away from subject
while you're learning RSpec.我建议您在学习 RSpec 时远离
subject
。 And you should not have to set payment.event
, it's already set in the factory.而且您不必设置
payment.event
,它已经在工厂中设置。
describe 'Association' do
expect(payment).to belong_to(:payable)
end
But I'm not aware of a belong_to
matcher.但我不知道
belong_to
匹配器。 You should not be directly checking implementation, but rather its behavior.您不应该直接检查实现,而应该检查它的行为。 The behavior you want is for
payment.payable
to return a Payable
.您想要的行为是
payment.payable
返回一个Payable
。
describe '#payable' do
it 'is a Payable' do
expect(payment.payable).to be_a(Payable)
end
end
The second failure is because you have incorrectly initialized your Payment.第二个失败是因为您错误地初始化了您的付款。 You're passing in
payable_id: event.id
but that does not set payable_type
.您正在传递
payable_id: event.id
,但未设置payable_type
。 Without payable_type
it doesn't know what class the ID is for.如果没有
payable_type
,它不知道ID 的用途是什么class。
Instead, pass the objects in directly.相反,直接传递对象。
let!(:payment) {
FactoryBot.build(:payment,
amount: 300,
method: 'cash',
payer: user,
payable: event,
status: 1,
)
}
Some more general cleanups...一些更一般的清理...
let!
will always run the block whether it's used or not.let
and the blocks will run as needed.let
并且块将根据需要运行。payable
to exist, so validate the presence of payable
.payable
存在,因此请验证payable
的存在。class Payment < ApplicationRecord
belongs_to :payable, polymorphic: true
validates :payable, presence: true
validates :amount, numericality: {
less_than_or_equal_to: balance,
message: "must be less than or equal to the balance of #{balance}"
}
end
require 'rails_helper'
RSpec.describe Payment, type: :model do
let(:event) {
create(:event, event_type: 'test', total: 10000, balance: 10000)
}
let(:user) { create(:user) }
let(:payment) {
build(:payment,
amount: 300,
method: 'cash',
payer: user,
payable: event,
status: 1
)
}
# It's useful to organize tests by method.
describe '#payable' do
it 'is a Payable' do
expect(payment.payable).to be_a(Payable)
end
end
describe '#amount' do
# Contexts also help organize and name your tests.
context 'when the amount is higher than the payable balance' do
# This code will run before each example.
before {
# Rather than hard coding numbers, make your tests relative.
# If event.balance changes the test will still work.
payment.amount = payment.payable.balance + 1
}
it 'is invalid' do
expect(payment.valid?).to be false
expect(payment.errors[:amount]).to include("must be less than or equal to")
end
end
end
end
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.