How to disable belongs_to :touch option in Rspec tests for Rails models?

谁说我不能喝 提交于 2019-12-04 15:09:46
DNNX

Assuming you're on Rails 4.1.4 or newer:

User.no_touching do
  Post.last.save
end

or even

ActiveRecord::Base.no_touching do
  Post.last.save
end

See ActiveRecord::NoTouching.

I disagree with the notion of altering the code for test purposes. Testing from my point of view should be an independent procedure.

As I see it you should provide a way to your test suite to alter the behavior of a model only for certain cases.

The following code

class Book < ActiveRecord::Base
  belongs_to :author, touch: true
end

class Author < ActiveRecord::Base
  has_many :books
end

which is your case will define an instance method

belongs_to_touch_after_save_or_destroy_for_author

behind the scene for the Book class. ( thanks to AR http://apidock.com/rails/ActiveRecord/Associations/Builder/BelongsTo/add_touch_callbacks )

So in your test code you could override that method to do something different or nothing at all!

In my case I use Rspec with FactoryGirl, so what I did was to create a special factory trait for the Book class which redefines belongs_to_touch_after_save_or_destroy_for_author for that object

FactoryGirl.define do
  factory :book do
    ...
    ...
  end

  trait :no_touch do
    before(:create) do |book_no_touch|
      def book_no_touch.belongs_to_touch_after_save_or_destroy_for_author
        true
      end
    end
  end
end

That way, when you need to test something where touching the related objects is irrelevant, you can create a book object with that factory

book = FactoryGirl.create(:book, :no_touch)

For Rails >= 4.2

Thanks to @Dorian, in Rails 4.2 the way to go is using ActiveRecord::NoTouching.

For Rails < 4.2

My working code in rspec support file:

# /spec/support/active_record_extensions.rb
class ActiveRecord::Base
  def self.without_touch_for_association(association, &block)
    method_name = :"belongs_to_touch_after_save_or_destroy_for_#{association}"
    return unless self.instance_methods.include?(method_name)

    method = self.send(:instance_method, method_name)
    self.send(:define_method, method_name) { true }

    yield

    self.send(:define_method, method_name, method)
    nil
  end

  def self.disable_touch_associations!
    associations = self.reflect_on_all_associations(:belongs_to)
    associations.each do |association|
      self.without_touch_for_association association.name do
        return
      end
    end
    nil
  end
end

Add this to your ./spec/spec_helper.rb to disable all touch calls for any model defined, for the whole test suite:

RSpec.configure do |config|
  if ENV['SILENCE_TOUCHES']
    config.before :suite do
      ActiveRecord::Base.descendants.each {|model| model.disable_touch_associations! }
    end
  end
end

Temporarely disabling a touch for a model and association in a particular test.

Post.without_touch_for_association(:user) do
  Post.last.save
end

Thanks to @xlembouras below for pointing me to the right direction!

I'm playing with this feature on our tests and I'm noticing a 25% reduction in test suite speed, for a 30min test suite. I may post more accurate results after more thorough research.

I'm not sure if this is going to work but you could try the following:

belongs_to :foo, touch: APP_CONFIG['doll_touch']

where APP_CONFIG is an application parameter that is set following this guide.

So, in your production/development part of the configuration, you set doll_touch to true and in your test to false.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!