Заглушка / насмешка над глобальными константами в RSpec - PullRequest
5 голосов
/ 21 октября 2011

У меня есть гем, у которого есть метод, который действует по-разному в зависимости от Rails.env:

def self.env
  if defined?(Rails)
    Rails.env
  elsif ...

А теперь я хотел бы написать спецификацию, которая проверяет этот путь кода. В настоящее время я делаю это так:

Kernel.const_set(:Rails, nil)
Rails.should_receive(:env).and_return('production')
...

И это нормально, просто ужасно. Другой способ - объявить это в spec_helper:

module Rails; end

И это тоже работает. Но, может быть, есть лучший способ? В идеале это должно работать:

rails = double('Rails')
rails.should_receive(:env).and_return('production')

Но, ну, это не так. А может я что-то не так делаю?

Ответы [ 4 ]

9 голосов
/ 21 октября 2011

Согласно различным твитам об этом, включение констант является , как правило, плохой идеей, потому что это затрудняет тестирование, и для этого необходимо изменить состояние констант (что делает их чуть менее постоянными).Тем не менее, если вы пишете плагин, который должен вести себя по-разному в зависимости от среды, в которой он загружен, вам придется где-то проверить наличие Rails, Merb и т. Д., Даже еслиэто не в этой части кода.Где бы это ни было, вы хотите сохранить его изолированным, чтобы решение принималось только один раз.Что-то вроде MyPlugin::env.Теперь вы можете безопасно заглушить этот метод в большинстве мест, а затем указать этот метод, заглушив константы.

Что касается того, как заглушить константы, ваш пример выглядит не совсем правильно.Код спрашивает, если defined?(Rails), но Kernel.const_set(:Rails, nil) не определяет константу, а просто устанавливает ее значение на nil.То, что вы хотите, это что-то вроде этого (отказ от ответственности - это не в моей голове, не проверено, даже не запускается, может содержать синтаксические ошибки, и не очень хорошо учтено):

def without_const(const)
  if Object.const_defined?(const)
    begin
      @const = const
      Object.send(:remove_const, const)
      yield
    ensure
      Object.const_set(const, @const)
    end
  else
    yield
  end
end

def with_stub_const(const, value)
  if Object.const_defined?(const)
    begin
      @const = const
      Object.const_set(const, value)
      yield
    ensure
      Object.const_set(const, @const)
    end
  else
    begin
      Object.const_set(const, value)
      yield
    ensure
      Object.send(:remove_const, const)
    end
  end
end

describe "..." do
  it "does x if Rails is defined" do
    rails = double('Rails', :env => {:stuff_i => 'need'})
    with_stub_const(:Rails, rails) do
      # ...
    end
  end

  it "does y if Rails is not defined" do
    without_const(:Rails) do
      # ....
    end
  end
end

Я дамнекоторые думали, стоит ли включать это в rspec или нет.Это одна из тех вещей, которые, если бы мы добавили людей, использовали бы ее как предлог, чтобы полагаться на константы, когда им не нужно:)

1 голос
/ 21 октября 2011

Для проверки условия, в котором определен Rails

 mock_rails = mock(:env => mock)
 Kernel.stub(:Rails).and_return(mock_rails)

Чтобы проверить условие, когда Rails не определен, я полагаю, что вам не нужно ничего делать, в противном случае вы бы определили Rails, а затем условие if (Rails) if было бы снова истинно

0 голосов
/ 21 июня 2016

Современный способ создания глобальных констант в RSpec включает использование stub_const.Допустим, у вас есть метод, использующий Rails, который вы хотели бы протестировать в среде, где Rails не определено (например, rubygem):

def MyClass
  def cache_value key, value
    Rails.cache.write key, value
  end
end

Вы можете написать спецификацию для этоготакой метод:

it 'writes the value to the cache' do
  key = :key
  value = 'abc'

  cache = double 'cache'
  rails = double 'Rails', cache: cache

  stub_const('Rails', rails)

  expect(cache).to receive(:write).with(key, value)

  MyClass.new.cache_value key, value
end
0 голосов
/ 06 января 2012

Ответ Дэвида очень мне помог.Я немного изменил его для обработки нескольких констант:

# Mock a constant within the passed block
# @example mock RAILS_ENV constant
#   it "does not allow links to be added in production environment" do
#     with_constants :RAILS_ENV => 'production' do
#       get :add, @nonexistent_link.url
#       response.should_not be_success
#     end
#   end
# @note adapted from:
#   * https://stackoverflow.com/a/7849835/457819
#   * http://digitaldumptruck.jotabout.com/?p=551
def with_constants(constants)
  @constants_to_restore = {}
  @constants_to_unset   = []

  constants.each do |name, val|
    if Object.const_defined?(name)
      @constants_to_restore[name] = Object.const_get(name)
    else
      @constants_to_unset << name
    end

    Object.const_set( name, val )
  end

  begin
    yield
  ensure
    @constants_to_restore.each do |name, val|
      Object.const_set( name, val )
    end

    @constants_to_unset.each do |name|
      Object.send(:remove_const, name)
    end
  end
end

def without_constants(constants)
  @constants_to_restore = {}

  constants.each do |name, val|
    if Object.const_defined?(name)
      @constants_to_restore[name] = Object.const_get(name)
    end

    Object.send(:remove_const, name)
  end

  begin
    yield
  ensure
    @constants_to_restore.each do |name, val|
      Object.const_set( name, val )
    end
  end
end
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...