通过RSpec& amp;模型验证的has_many的TDD工厂女工

时间:2011-11-11 21:53:07

标签: ruby-on-rails rspec factory-bot has-many-through

请考虑以下事项:

ScheduledSession ------> Applicant <------ ApplicantSignup

注意事项:

  1. 系统中始终存在ScheduledSession;把它想象成一门课程或课程。
  2. 此处的目的是在signups_controller#create期间针对ScheduledSession上的属性验证ApplicantSignup模型
  3. 协会

    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范围之外的东西,或者有更好的方法来解决这个问题吗?感谢所有评论,建议和建设性的批评!

    更新

    • 不确定这是什么,但这是至少与how it's implemented at the controller level
    • 相关的方法
    • 我需要考虑至少忽略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 
    

2 个答案:

答案 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_sessi‌​on]};

因此,DB访问次数会减少,您将测试ApplicantSignup,而不是申请人。