请考虑以下事项:
ScheduledSession ------> Applicant <------ ApplicantSignup
注意事项:
signups_controller#create
期间针对ScheduledSession上的属性验证ApplicantSignup模型协会
class ScheduledSession < ActiveRecord::Base
has_many :applicants, :dependent => :destroy
has_many :applicant_signups, :through => :applicants
#...
end
class ApplicantSignup < ActiveRecord::Base
has_many :applicants, :dependent => :destroy
has_many :scheduled_sessions, :through => :applicants
#...
end
class Applicant < ActiveRecord::Base
belongs_to :scheduled_session
belongs_to :applicant_signup
# TODO: enforce validations for presence
# and uniqueness constraints etc.
#...
end
SignupsController
资源是RESTful,即#create
操作的路径类似于/scheduled_sessions/:id/signups/new
def new
@session = ScheduledSession.find(params[:scheduled_session_id])
@signup = @session.signups.new
end
def create
@session = ScheduledSession.find(params[:scheduled_session_id])
@session.duration = (@session.end.to_time - @session.start.to_time).to_i
@signup = ApplicantSignup.new(params[:signup].merge(:sessions => [@session]))
if @signup.save
# ...
else
render :new
end
end
您会注意到我将虚拟属性设置为@session.duration
以上,以防止会话被视为无效。真正的魔力&#39;如果您将在@signup = ApplicantSignup.new(params[:signup].merge(:sessions => [@session]))
中发生,现在意味着在模型中我可以从self.scheduled_sessions
中选择并访问ScheduledSession,这个ApplicantSignup正在构建,即使在这个时间点,那里连接表中没有记录。
模型验证例如看起来像
def ensure_session_is_upcoming
errors[:base] << "Cannot signup for an expired session" unless self.scheduled_sessions.select { |r| r.upcoming? }.size > 0
end
def ensure_published_session
errors[:base] << "Cannot signup for an unpublished session" if self.scheduled_sessions.any? { |r| r.published == false }
end
def validate_allowed_age
# raise StandardError, self.scheduled_sessions.inspect
if self.scheduled_sessions.select { |r| r.allowed_age == "adults" }.size > 0
errors.add(:dob_year) unless (dob_year.to_i >= Time.now.strftime('%Y').to_i-85 && dob_year.to_i <= Time.now.strftime('%Y').to_i-18)
# elsif ... == "children"
end
end
上述内容在development
中运行良好,验证工作正常 - 但是如何使用Factory Girl进行测试?我希望单元测试能够保证我已经实现的业务逻辑 - 当然,这是事后但仍然是TDD的一种方式。
您会注意到我在上一次验证中已经注释了raise StandardError, self.scheduled_sessions.inspect
- 这会为[]
返回self.scheduled_sessions
,这表示我的工厂设置只是不对。
众多尝试之一=)
it "should be able to signup to a session" do
scheduled_session = Factory.build(:scheduled_session)
applicant_signup = Factory.build(:applicant_signup)
applicant = Factory.create(:applicant, :scheduled_session => scheduled_session, :applicant_signup => applicant_signup)
applicant_signup.should be_valid
end
it "should be able to signup to a session for adults if between 18 and 85 years" do
scheduled_session = Factory.build(:scheduled_session)
applicant_signup = Factory.build(:applicant_signup)
applicant_signup.dob_year = 1983 # 28-years old
applicant = Factory.create(:applicant, :scheduled_session => scheduled_session, :applicant_signup => applicant_signup)
applicant_signup.should have(0).error_on(:dob_year)
end
第一个通过,但老实说,我不相信它正确验证了applicant_signup模型; self.scheduled_sessions
正在返回[]
的事实仅仅意味着上述内容并不正确。
我很有可能尝试测试Factory Girl范围之外的东西,或者有更好的方法来解决这个问题吗?感谢所有评论,建议和建设性的批评!
更新
scheduled_session
模型上的scheduled_sessions
来返回applicant_signup
。工厂
FactoryGirl.define do
factory :signup do
title "Mr."
first_name "Franklin"
middle_name "Delano"
last_name "Roosevelt"
sequence(:civil_id) {"#{'%012d' % Random.new.rand((10 ** 11)...(10 ** 12))}"}
sequence(:email) {|n| "person#{n}@#{(1..100).to_a.sample}example.com" }
gender "male"
dob_year "1980"
sequence(:phone_number) { |n| "#{'%08d' % Random.new.rand((10 ** 7)...(10 ** 8))}" }
address_line1 "some road"
address_line2 "near a pile of sand"
occupation "code ninja"
work_place "Dharma Initiative"
end
factory :session do
title "Example title"
start DateTime.civil_from_format(:local,2011,12,27,16,0,0)
duration 90
language "Arabic"
slides_language "Arabic & English"
venue "Main Room"
audience "Diabetic Adults"
allowed_age "adults"
allowed_gender "both"
capacity 15
published true
after_build do |session|
# signups will be assigned manually on a per test basis
# session.signups << FactoryGirl.build(:signup, :session => session)
end
end
factory :applicant do
association :session
association :signup
end
#...
end
答案 0 :(得分:0)
我之前的假设是正确的,只有很小的变化:
我需要考虑忽略工厂女孩的协会方面 至少并尝试通过 存根 返回scheduled_session 在applicant_signup模型上的scheduled_sessions。
让我的测试非常简单:
it "should be able to applicant_signup to a scheduled_session" do
scheduled_session = Factory(:scheduled_session)
applicant_signup = Factory.build(:applicant_signup)
applicant_signup.stub!(:scheduled_sessions).and_return{[scheduled_session]}
applicant_signup.should be_valid
end
it "should be able to applicant_signup to a scheduled_session for adults if between 18 and 85 years" do
scheduled_session = Factory(:scheduled_session)
applicant_signup = Factory.build(:applicant_signup)
applicant_signup.dob_year = 1983 # 28-years old
applicant_signup.stub!(:scheduled_sessions).and_return{[scheduled_session]}
applicant_signup.should have(0).error_on(:dob_year)
applicant_signup.should be_valid
end
并且该测试特别需要类似的方法:
it "should not be able to applicant_signup if the scheduled_session capacity has been met" do
scheduled_session = Factory.build(:scheduled_session, :capacity => 3)
scheduled_session.stub_chain(:applicant_signups, :count).and_return(3)
applicant_signup = Factory.build(:applicant_signup)
applicant_signup.stub!(:scheduled_sessions).and_return{[scheduled_session]}
applicant_signup.should_not be_valid
end
...并且成功 - 忽略测试持续时间,因为spork
会导致错误的报告。
Finished in 2253.64 seconds
32 examples, 0 failures, 3 pending
Done.
答案 1 :(得分:0)
作为另一种方法,您可以使用Rspecs stub_model
。
另外,如果您测试ApplicantSignup
,则应该初始化它,而不是测试Applicant
的创建。例如:
applicant_signup = Factory.build(:applicant_signup);
applicant_signup.should_receive(:scheduled_sessions)
.and_return{[scheduled_session]};
因此,DB访问次数会减少,您将测试ApplicantSignup,而不是申请人。