Rails Rspec - 如何设置多态has_many关联
Rails Rspec - How to set polymorphic has_many association
我有一个模型(付款)通过多态关联属于另一个模型(事件)。
一些测试失败,因为所有者模型(事件)在验证中被支付模型访问,但事件返回 nil。直接在浏览器中测试应用程序时,所有功能都可以正常工作。
我在下面的 payment.rb
中添加了一些评论。
我试过在工厂中定义关联,但没有成功。
在规范中设置此关联的最佳方法是什么?
# 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
本规范中的两个示例均失败。
# 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
输出
# 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
更新
根据 Schwern 的反馈通过规范。
仍然对金额使用自定义验证,因为 balance
是关联 payable
上的一个字段,而不是 payment
上的一个字段(无法找到从内部访问关联模型的方法-在验证助手中)
# 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
您的第一个测试并没有像您认为的那样失败。它在下一行失败,is_expected.to belong_to(:payable)
.
您正在设置 payment
,但您正在测试 implicitly defined subject,这将是 Payment.new
。
is_expected.to belong_to(:payable)
相当于...
expect(subject).to belong_to(:payable)
并且由于您没有定义 subject
这是...
expect(Payment.new).to belong_to(:payable)
Payment.new
没有定义 payable
,因此 amount_is_valid
验证错误。
要解决此问题,请直接测试 payment
。我建议您在学习 RSpec 时远离 subject
。而且你不应该设置payment.event
,它已经在工厂设置了。
describe 'Association' do
expect(payment).to belong_to(:payable)
end
但我不知道 belong_to
匹配器。您不应该直接检查实现,而应该检查它的行为。您想要的行为是 payment.payable
到 return a Payable
.
describe '#payable' do
it 'is a Payable' do
expect(payment.payable).to be_a(Payable)
end
end
第二次失败是因为您没有正确初始化您的支付。您正在传递 payable_id: event.id
,但不会设置 payable_type
。没有 payable_type
它不知道 class 这个 ID 的用途。
相反,直接传递对象。
let!(:payment) {
FactoryBot.build(:payment,
amount: 300,
method: 'cash',
payer: user,
payable: event,
status: 1,
)
}
一些更一般的清理...
let!
将始终 运行 块,无论是否使用。除非您特别需要,否则请使用 let
并且块将根据需要 运行。
- 您希望
payable
存在,因此验证 payable
是否存在。
- 使用内置的 numericality validator 金额。
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
我有一个模型(付款)通过多态关联属于另一个模型(事件)。
一些测试失败,因为所有者模型(事件)在验证中被支付模型访问,但事件返回 nil。直接在浏览器中测试应用程序时,所有功能都可以正常工作。
我在下面的 payment.rb
中添加了一些评论。
我试过在工厂中定义关联,但没有成功。
在规范中设置此关联的最佳方法是什么?
# 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
本规范中的两个示例均失败。
# 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
输出
# 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
更新
根据 Schwern 的反馈通过规范。
仍然对金额使用自定义验证,因为 balance
是关联 payable
上的一个字段,而不是 payment
上的一个字段(无法找到从内部访问关联模型的方法-在验证助手中)
# 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
您的第一个测试并没有像您认为的那样失败。它在下一行失败,is_expected.to belong_to(:payable)
.
您正在设置 payment
,但您正在测试 implicitly defined subject,这将是 Payment.new
。
is_expected.to belong_to(:payable)
相当于...
expect(subject).to belong_to(:payable)
并且由于您没有定义 subject
这是...
expect(Payment.new).to belong_to(:payable)
Payment.new
没有定义 payable
,因此 amount_is_valid
验证错误。
要解决此问题,请直接测试 payment
。我建议您在学习 RSpec 时远离 subject
。而且你不应该设置payment.event
,它已经在工厂设置了。
describe 'Association' do
expect(payment).to belong_to(:payable)
end
但我不知道 belong_to
匹配器。您不应该直接检查实现,而应该检查它的行为。您想要的行为是 payment.payable
到 return a Payable
.
describe '#payable' do
it 'is a Payable' do
expect(payment.payable).to be_a(Payable)
end
end
第二次失败是因为您没有正确初始化您的支付。您正在传递 payable_id: event.id
,但不会设置 payable_type
。没有 payable_type
它不知道 class 这个 ID 的用途。
相反,直接传递对象。
let!(:payment) {
FactoryBot.build(:payment,
amount: 300,
method: 'cash',
payer: user,
payable: event,
status: 1,
)
}
一些更一般的清理...
let!
将始终 运行 块,无论是否使用。除非您特别需要,否则请使用let
并且块将根据需要 运行。- 您希望
payable
存在,因此验证payable
是否存在。 - 使用内置的 numericality validator 金额。
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