Пропустить обратные вызовы на Factory Girl и Rspec - PullRequest
94 голосов
/ 06 января 2012

Я тестирую модель с обратным вызовом после создания, который я хотел бы запускать только в некоторых случаях во время тестирования.Как я могу пропустить / запустить обратные вызовы с завода?

class User < ActiveRecord::Base
  after_create :run_something
  ...
end

Фабрика:

FactoryGirl.define do
  factory :user do
    first_name "Luiz"
    last_name "Branco"
    ...
    # skip callback

    factory :with_run_something do
      # run callback
  end
end

Ответы [ 15 ]

107 голосов
/ 12 января 2012

Я не уверен, что это лучшее решение, но я успешно достиг этого, используя:

FactoryGirl.define do
  factory :user do
    first_name "Luiz"
    last_name "Branco"
    #...

    after(:build) { |user| user.class.skip_callback(:create, :after, :run_something) }

    factory :user_with_run_something do
      after(:create) { |user| user.send(:run_something) }
    end
  end
end

Запуск без обратного вызова:

FactoryGirl.create(:user)

Запуск с обратным вызовом:

FactoryGirl.create(:user_with_run_something)
81 голосов
/ 27 ноября 2012

Если вы не хотите выполнять обратный вызов, сделайте следующее:

User.skip_callback(:create, :after, :run_something)
Factory.create(:user)

Имейте в виду, что skip_callback будет сохраняться в других спецификациях после его запуска, поэтому рассмотрите что-то вроде следующего:

before do
  User.skip_callback(:create, :after, :run_something)
end

after do
  User.set_callback(:create, :after, :run_something)
end
32 голосов
/ 22 февраля 2016

Ни одно из этих решений не подходит.Они портят класс, удаляя функциональность, которую следует удалить из экземпляра, а не из класса.

factory :user do
  before(:create){|user| user.define_singleton_method(:send_welcome_email){}}

Вместо подавления обратного вызова, я подавляю функциональность обратного вызова.В каком-то смысле мне больше нравится этот подход, потому что он более явный.

25 голосов
/ 07 апреля 2014

Я бы хотел улучшить ответ @luizbranco, чтобы сделать обратный вызов after_save более пригодным для повторного использования при создании других пользователей.

FactoryGirl.define do
  factory :user do
    first_name "Luiz"
    last_name "Branco"
    #...

    after(:build) { |user| 
      user.class.skip_callback(:create, 
                               :after, 
                               :run_something1,
                               :run_something2) 
    }

    trait :with_after_save_callback do
      after(:build) { |user| 
        user.class.set_callback(:create, 
                                :after, 
                                :run_something1,
                                :run_something2) 
      }
    end
  end
end

Работа без обратного вызова after_save:

FactoryGirl.create(:user)

Запуск с обратным вызовом after_save:

FactoryGirl.create(:user, :with_after_save_callback)

В моем тесте я предпочитаю создавать пользователей без обратного вызова по умолчанию, потому что используемые методы запускают дополнительные вещи, которые я обычно не хочу в моих тестовых примерах.

---------- ОБНОВЛЕНИЕ ------------ Я перестал использовать skip_callback, поскольку в наборе тестов были некоторые проблемы с несогласованностью.

Альтернативное решение 1(использование заглушки и отката):

after(:build) { |user| 
  user.class.any_instance.stub(:run_something1)
  user.class.any_instance.stub(:run_something2)
}

trait :with_after_save_callback do
  after(:build) { |user| 
    user.class.any_instance.unstub(:run_something1)
    user.class.any_instance.unstub(:run_something2)
  }
end

Альтернативное решение 2 (мой предпочтительный подход):

after(:build) { |user| 
  class << user
    def run_something1; true; end
    def run_something2; true; end
  end
}

trait :with_after_save_callback do
  after(:build) { |user| 
    class << user
      def run_something1; super; end
      def run_something2; super; end
    end
  }
end
6 голосов
/ 29 августа 2013

Это решение работает для меня, и вам не нужно добавлять дополнительный блок в определение фабрики:

user = FactoryGirl.build(:user)
user.send(:create_without_callbacks) # Skip callback

user = FactoryGirl.create(:user)     # Execute callbacks
5 голосов
/ 20 ноября 2014

Для меня лучше всего подойдет простая заглушка в Rspec 3

allow(User).to receive_messages(:run_something => nil)
4 голосов
/ 28 марта 2017
FactoryGirl.define do
  factory :order, class: Spree::Order do

    trait :without_callbacks do
      after(:build) do |order|
        order.class.skip_callback :save, :before, :update_status!
      end

      after(:create) do |order|
        order.class.set_callback :save, :before, :update_status!
      end
    end
  end
end

Важное примечание Вы должны указать оба из них. Если использовать только до и запустить несколько спецификаций, он попытается отключить обратный вызов несколько раз. В первый раз это удастся, но во второй раз обратный вызов больше не будет определяться. Так что это будет ошибка

4 голосов
/ 05 марта 2013

Вызов skip_callback с моей фабрики оказался проблематичным для меня.

В моем случае у меня есть класс документа с некоторыми обратными вызовами, связанными с s3, до и после создания, который я хочу запускать только при тестировании полного стека.является необходимым.В противном случае, я хочу пропустить эти обратные вызовы s3.

Когда я попробовал skip_callbacks на своей фабрике, он сохранил этот обратный вызов, даже когда я создал объект документа напрямую, без использования фабрики.Вместо этого я использовал заглушки mocha в вызове после сборки, и все работает отлично:

factory :document do
  upload_file_name "file.txt"
  upload_content_type "text/plain"
  upload_file_size 1.kilobyte
  after(:build) do |document|
    document.stubs(:name_of_before_create_method).returns(true)
    document.stubs(:name_of_after_create_method).returns(true)
  end
end
3 голосов
/ 18 марта 2016

Ответ Джеймса Шевалье о том, как пропустить обратный вызов before_validation, мне не помог, поэтому, если вы отстаете от меня, вот рабочее решение:

в модели:

before_validation :run_something, on: :create

на фабрике:

after(:build) { |obj| obj.class.skip_callback(:validation, :before, :run_something) }
3 голосов
/ 31 июля 2013

Это будет работать с текущим синтаксисом rspec (на данный момент) и будет намного чище:

before do
   User.any_instance.stub :run_something
end
...