Factory Girl ve Rspec'te geri aramaları atla


103

Bir modeli, test sırasında yalnızca bazı durumlarda çalıştırmak istediğim bir oluşturma sonrası geri arama ile test ediyorum. Bir fabrikadan geri aramaları nasıl atlayabilir / çalıştırabilirim?

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

Fabrika:

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

    factory :with_run_something do
      # run callback
  end
end

Yanıtlar:


111

Bunun en iyi çözüm olup olmadığından emin değilim, ancak bunu kullanarak başarıyla başardım:

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

Geri aranmadan çalıştırma:

FactoryGirl.create(:user)

Geri aramayla çalıştırılıyor:

FactoryGirl.create(:user_with_run_something)

3
Bir :on => :createdoğrulamayı atlamak istiyorsanız , şunu kullanınafter(:build) { |user| user.class.skip_callback(:validate, :create, :after, :run_something) }
James Chevalier

8
Atlayan geri arama mantığını tersine çevirmek daha iyi olmaz mıydı? Demek istediğim, varsayılan değer, bir nesne oluşturduğumda geri aramaların tetiklenmesi ve istisnai durum için farklı bir parametre kullanmam gerektiği olmalıdır. bu nedenle FactoryGirl.create (: user) geri aramaları tetikleyen kullanıcıyı oluşturmalı ve FactoryGirl.create (: user_without_callbacks) kullanıcıyı geri aramalar olmadan oluşturmalıdır. Bunun sadece bir "tasarım" değişikliği olduğunu biliyorum, ancak bunun önceden var olan kodu kırmayı önleyebileceğini ve daha tutarlı olabileceğini düşünüyorum.
Gnagno

3
@ Minimal'in çözümünün belirttiği gibi, Class.skip_callbackarama diğer testlerde kalıcı olacaktır, bu nedenle diğer testleriniz geri aramanın gerçekleşmesini beklerse, atlayan geri arama mantığını tersine çevirmeye çalışırsanız başarısız olur.
mpdaugherty

Blokta Mocha ile stublama konusunda @ uberllama'nın cevabını kullandım after(:build). Bu, fabrika varsayılanınızın geri aramayı çalıştırmasına izin verir ve her kullanımdan sonra geri aramanın sıfırlanmasını gerektirmez.
mpdaugherty

Bunun başka şekilde çalıştığına dair herhangi bir fikrin var mı? stackoverflow.com/questions/35950470/…
Chris Hough

90

Geri arama yapmak istemediğinizde aşağıdakileri yapın:

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

Skip_callback'in çalıştırıldıktan sonra diğer spesifikasyonlarda devam edeceğini unutmayın, bu nedenle aşağıdaki gibi bir şey düşünün:

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

after do
  User.set_callback(:create, :after, :run_something)
end

12
Bu yanıtı daha çok seviyorum çünkü geri aramaları atlamanın sınıf düzeyinde takıldığını ve bu nedenle sonraki testlerde geri aramaları atlamaya devam edeceğini açıkça belirtiyor.
siannopollo

Bunu ben de daha çok seviyorum. Fabrikamın kalıcı olarak farklı davranmasını istemiyorum. Belirli bir dizi test için atlamak istiyorum.
theUtherSide

39

Bu çözümlerin hiçbiri iyi değil. Sınıftan değil, örnekten kaldırılması gereken işlevselliği kaldırarak sınıfı tahrif ederler.

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

Geri aramayı bastırmak yerine, geri aramanın işlevselliğini bastırıyorum. Bir bakıma, bu yaklaşımı daha açık olduğu için daha çok seviyorum.


1
Bu yanıtı gerçekten beğendim ve merak ediyorum, bunun gibi bir takma ad, niyetin hemen netleşmesini sağlamak için FactoryGirl'in bir parçası olmalı mı?
Giuseppe

Ben de bu yanıtı o kadar çok beğeniyorum ki, diğer her şeyi olumsuz oylarım, ama görünen o ki, geri arama türünüz ise around_*(örneğin user.define_singleton_method(:around_callback_method){|&b| b.call }) tanımlanmış yönteme bir blok geçmemiz gerekiyor .
2017

1
Sadece daha iyi bir çözüm değil, bazı nedenlerden dolayı diğer yöntem benim için çalışmadı. Bunu uyguladığımda, geri arama yönteminin olmadığını söyledi, ancak onu dışarıda bıraktığımda gereksiz istekleri saplamamı isteyecekti. Beni bir çözüme götürsem de bunun neden olabileceğini bilen var mı?
Babbz77

27

@Luizbranco'nun yanıtında, diğer kullanıcılar oluştururken after_save geri aramayı daha yeniden kullanılabilir hale getirmek için bir iyileştirme yapmak istiyorum.

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

Kaydetme sonrası geri araması olmadan çalıştırma:

FactoryGirl.create(:user)

Kaydetme sonrası geri arama ile çalıştırılıyor:

FactoryGirl.create(:user, :with_after_save_callback)

Testimde, varsayılan olarak geri arama olmadan kullanıcılar oluşturmayı tercih ediyorum çünkü kullanılan yöntemler normalde test örneklerimde istemediğim fazladan şeyler çalıştırıyor.

---------- GÜNCELLEME ------------ Test paketinde bazı tutarsızlık sorunları olduğu için skip_callback'i kullanmayı bıraktım.

Alternatif Çözüm 1 (saplama ve saplama kaldırma kullanımı):

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

Alternatif Çözüm 2 (tercih ettiğim yaklaşım):

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

Bunun başka şekilde çalıştığına dair herhangi bir fikrin var mı? stackoverflow.com/questions/35950470/…
Chris Hough

RuboCop, Alternative Solution 2 için "Style / SingleLineMethods: Tek satırlık yöntem tanımlarından kaçının" ile şikayet ediyor, bu yüzden biçimlendirmeyi değiştirmem gerekecek, ancak aksi takdirde mükemmel!
coberlin

15

Rails 5 - skip_callbackFactoryBot fabrikasından atlarken Argüman hatası yükseliyor.

ArgumentError: After commit callback :whatever_callback has not been defined

Rails 5'te skip_callback'in tanınmayan geri aramaları işleme şekliyle ilgili bir değişiklik oldu :

ActiveSupport :: Callbacks # skip_callback, tanınmayan bir geri arama kaldırılırsa artık bir ArgumentError hatası yükseltir

Ne zaman skip_callbackfabrikadan denir, AR modelinde gerçek geri arama henüz tanımlanmadı.

Her şeyi denediniz ve benim gibi dışarı saçını çektiğim ettiyseniz, burada çözümdür (FactoryBot sorunları arama aldım) ( NOT raise: falsebölüm ):

after(:build) { YourSweetModel.skip_callback(:commit, :after, :whatever_callback, raise: false) }

Tercih ettiğiniz diğer stratejilerle kullanmaktan çekinmeyin.


1
Harika, tam olarak bana olan buydu. Bir geri aramayı bir kez kaldırırsanız ve tekrar denerseniz, bunun gerçekleşeceğini, bu nedenle bunun bir fabrika için birden çok kez tetiklenme olasılığının yüksek olduğunu unutmayın.
slhck

6

Bu çözüm benim için çalışıyor ve Fabrika tanımınıza ek bir blok eklemenize gerek yok:

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

user = FactoryGirl.create(:user)     # Execute callbacks

5
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

Önemli not ikisini de belirtmelisiniz. Yalnızca daha önce kullanır ve birden çok özelliği çalıştırırsanız, geri aramayı birden çok kez devre dışı bırakmaya çalışır. İlk seferde başarılı olacak, ancak ikincisinde geri arama artık tanımlanmayacak. Bu yüzden hata yapacak


Bu, son projede bir pakette bazı karmaşık hatalara neden oldu - @ Sairam'ın cevabına benzer bir şey aldım, ancak geri arama, testler arasında sınıfta bırakılıyordu. Hata.
kfrz

5

Basit bir saplama benim için en iyi Rspec 3'te çalıştı

allow_any_instance_of(User).to receive_messages(:run_something => nil)

5
Sen için kurmak gerekiyordu durumlarda arasında User; :run_somethingbir sınıf yöntemi değildir.
PJSCopeland

4

Fabrikamdan skip_callback'i aramak benim için sorunlu oldu.

Benim durumumda, oluşturmadan önce ve sonra s3 ile ilgili bazı geri aramaları olan bir belge sınıfım var, yalnızca tam yığını test ederken çalıştırmak istiyorum. Aksi takdirde, bu s3 geri aramalarını atlamak istiyorum.

Fabrikamda skip_callbacks'i denediğimde, bir fabrika kullanmadan doğrudan bir belge nesnesi oluşturduğumda bile geri aramanın atlamasında ısrar etti. Bunun yerine, derleme sonrası çağrısında mocha saplamaları kullandım ve her şey mükemmel çalışıyor:

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

Burada bütün çözümlerin ve fabrika içinde mantık sahibi olmak için, bu çalışan tek before_validationkancaya (yapmaya çalışıyor skip_callbackFactoryGirl en herhangi biriyle beforeveya afterseçenekleri için buildve createçalışma vermedi)
Mike T

3

Bu, mevcut rspec sözdizimi ile çalışacak (bu yazı itibariyle) ve çok daha temiz:

before do
   User.any_instance.stub :run_something
end

Bu, Rspec 3'te kullanımdan kaldırılmıştır. Benim için çalışan normal bir saplama kullanmak, aşağıdaki cevabıma bakın.
samg

3

James Chevalier'in before_validation geri aramasının nasıl atlanacağına dair cevabı bana yardımcı olmadı, bu yüzden burada benimle aynı şeyi yaparsanız, işe yarayan çözüm:

modelde:

before_validation :run_something, on: :create

fabrikada:

after(:build) { |obj| obj.class.skip_callback(:validation, :before, :run_something) }

2
Bundan kaçınmanın tercih edildiğini düşünüyorum. Sınıfın her örneği için geri aramaları atlar (sadece fabrika kızı tarafından üretilenleri değil). Bu, hata ayıklaması zor olabilecek bazı spesifikasyon yürütme sorunlarına (yani devre dışı bırakma ilk fabrika oluşturulduktan sonra gerçekleşirse) yol açar. Spesifikasyon / destekte istenen davranış buysa, açıkça yapılmalıdır: Model.skip_callback(...)
Kevin Sylvestre

2

Benim durumumda, redis önbelleğime bir şeyler yükleyen geri arama var. Ancak test ortamım için çalışan bir redis örneğine sahip olmadım / istemedim.

after_create :load_to_cache

def load_to_cache
  Redis.load_to_cache
end

load_to_cacheDurumum için, yukarıdakine benzer şekilde, yöntemimi spec_helper'ımda şu şekilde yazdım:

Redis.stub(:load_to_cache)

Ayrıca, bunu test etmek istediğim belirli durumlarda, bunları karşılık gelen Rspec test durumlarının önceki bloğunda çözmem gerekiyor.

Senin içinde daha karmaşık bir şeyler after_createolabileceğini veya bunu çok zarif bulmayabileceğini biliyorum . Modelinizde tanımlanan geri aramayı, bu makalenin 'Geri aramaları iptal etme' bölümüne göre after_createmuhtemelen aynı geri aramayı ve geri dönüşü tanımlayabileceğiniz Fabrikanızda bir kanca tanımlayarak (factory_girl belgelerine bakın) iptal etmeyi deneyebilirsiniz . (Geri aramanın hangi sırayla yürütüleceğinden emin değilim, bu yüzden bu seçeneği tercih etmedim).false

Son olarak, (üzgünüm makaleyi bulamıyorum) Ruby, bir geri arama kancasını kaldırmak için bazı kirli meta programları kullanmanıza izin verir (sıfırlamanız gerekecek). Sanırım bu en az tercih edilen seçenek olacaktır.

Aslında bir çözüm olmayan bir şey daha var, ancak nesneyi gerçekten oluşturmak yerine, fabrika ayarlarınızda Factory.build ile kaçıp kurtulamayacağınıza bakın. (Yapabilirsen en basit olanı olurdu).


2

Yukarıda verilen cevapla ilgili olarak, https://stackoverflow.com/a/35562805/2001785 , kodu fabrikaya eklemenize gerek yoktur. Yöntemleri teknik özelliklerin kendisinde aşırı yüklemeyi daha kolay buldum. Örneğin, yerine (alıntı yapılan gönderideki fabrika kodu ile bağlantılı olarak)

let(:user) { FactoryGirl.create(:user) }

Kullanmayı seviyorum (belirtilen fabrika kodu olmadan)

let(:user) do
  FactoryGirl.build(:user).tap do |u|
      u.define_singleton_method(:send_welcome_email){}
      u.save!
    end
  end
end

Bu şekilde, testin davranışını anlamak için hem fabrikaya hem de test dosyalarına bakmanıza gerek kalmaz.


1

Geri arama bir sınıf düzeyinde çalıştırıldığı / ayarlandığı için aşağıdaki çözümü daha temiz bir yol olarak buldum.

# create(:user) - will skip the callback.
# create(:user, skip_create_callback: false) - will set the callback
FactoryBot.define do
  factory :user do
    first_name "Luiz"
    last_name "Branco"

    transient do
      skip_create_callback true
    end

    after(:build) do |user, evaluator|
      if evaluator.skip_create_callback
        user.class.skip_callback(:create, :after, :run_something)
      else
        user.class.set_callback(:create, :after, :run_something)
      end
    end
  end
end

0

İşte bunu genel bir şekilde ele almak için oluşturduğum bir pasaj.
Raylarla ilgili geri aramalar da dahil olmak üzere yapılandırılan her geri aramayı atlayacaktır before_save_collection_association, ancak otomatik oluşturulan autosave_associated_records_for_geri aramalar gibi ActiveRecord'un düzgün çalışması için gereken bazılarını atlamayacaktır .

# In some factories/generic_traits.rb file or something like that
FactoryBot.define do
  trait :skip_all_callbacks do
    transient do
      force_callbacks { [] }
    end

    after(:build) do |instance, evaluator|
      klass = instance.class
      # I think with these callback types should be enough, but for a full
      # list, check `ActiveRecord::Callbacks::CALLBACKS`
      %i[commit create destroy save touch update].each do |type|
        callbacks = klass.send("_#{type}_callbacks")
        next if callbacks.empty?

        callbacks.each do |cb|
          # Autogenerated ActiveRecord after_create/after_update callbacks like
          # `autosave_associated_records_for_xxxx` won't be skipped, also
          # before_destroy callbacks with a number like 70351699301300 (maybe
          # an Object ID?, no idea)
          next if cb.filter.to_s =~ /(autosave_associated|\d+)/

          cb_name = "#{klass}.#{cb.kind}_#{type}(:#{cb.filter})"
          if evaluator.force_callbacks.include?(cb.filter)
            next Rails.logger.debug "Forcing #{cb_name} callback"
          end

          Rails.logger.debug "Skipping #{cb_name} callback"
          instance.define_singleton_method(cb.filter) {}
        end
      end
    end
  end
end

daha sonra:

create(:user, :skip_all_callbacks)

Söylemeye gerek yok, YMMV, bu yüzden test günlüklerine gerçekten neyi atladığına bir bak. Belki de gerçekten ihtiyacınız olan bir geri aramayı ekleyen bir cevheriniz vardır ve testlerinizin sefil bir şekilde başarısız olmasına neden olur veya 100 geri arama şişman modelinizden belirli bir test için sadece bir çifte ihtiyacınız vardır. Bu durumlarda, geçici olanı deneyin:force_callbacks

create(:user, :skip_all_callbacks, force_callbacks: [:some_important_callback])

BONUS

Bazen doğrulamaları da atlamanız gerekir (hepsi testleri daha hızlı yapmak için), ardından şunu deneyin:

  trait :skip_validate do
    to_create { |instance| instance.save(validate: false) }
  end

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

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

trait :user_with_run_something do
  after(:create) { |user| user.class.set_callback(:create, :after, :run_something) }
  end
 end
end

Geri aramayı, çalıştırmak istediğinizde bu örnekler için bir özellik ile ayarlayabilirsiniz.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.