TDD has_many через проверки моделей с RSpec & Factory Girl - PullRequest
4 голосов
/ 12 ноября 2011

Рассмотрим следующее:

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

Примечания:

  1. Запланированная сессия будет существовать в системе всегда; думайте об этом как о классе или курсе.
  2. Целью здесь является проверка модели ApplicantSignup по атрибуту ScheduledSession во время signups_controller#create

ассоциации

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, чтобы сессия не считалась недействительной. Настоящая «магия», если вы будете происходить в @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, и проверки работают как ожидалось & mdash; но как можно проверить с Factory Girl? Я хочу, чтобы модульные тесты гарантировали бизнес-логику, которую я реализовал после всего & mdash; конечно, это после факта, но все еще является одним из способов достижения TDD.

Вы заметите, что я получил комментарий raise StandardError, self.scheduled_sessions.inspect в последней проверке выше & mdash; это возвращает [] для 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, или есть гораздо лучший подход к решению этой проблемы? Ценю все комментарии, советы и конструктивную критику!

Обновление:

  • Не уверен, как это называется, но этот подход используется по крайней мере в отношении , как это реализовано на уровне контроллера
  • Мне нужно подумать о игнорировании 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 голосов
/ 16 ноября 2011

В качестве другого подхода вы можете использовать Rspecs stub_model.

Кроме того, если вы тестируете ApplicantSignup, вы должны инициировать его, а не тестировать создание Applicant. Например:

applicant_signup = Factory.build(:applicant_signup);

applicant_signup.should_receive(:scheduled_sessions)
                           .and_return{[scheduled_sessi‌​on]};

Таким образом, будет меньше доступа к БД, и вы будете тестировать ApplicantSignup, а не Applicant.

0 голосов
/ 16 ноября 2011

Мое предыдущее предположение было верным, с небольшими изменениями:

Мне нужно рассмотреть вопрос об игнорировании Factory Girl для аспекта ассоциации в минимум и попытка вернуть запланированную сессию заглушка плановые_сессии в модели Applicant_signup.

сделать мои тесты довольно просто:

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

... и успех & mdash; игнорируйте продолжительность тестирования, поскольку spork вызывает ложное сообщение об этом.

Finished in 2253.64 seconds
32 examples, 0 failures, 3 pending
Done.
...