Может ли метод-заглушка RSpec возвращать разные значения в последовательности? - PullRequest
61 голосов
/ 10 мая 2011

У меня есть семейство моделей с методом location, который объединяет location выходы других объектов-членов.(Участники связаны с семьями, но здесь это не важно.)

Например, если

  • member_1 имеет location == 'Сан-Диего (в пути, возвращается 15 мая)'
  • member_2 имеет location ==' Сан-Диего '

Family.location может вернуть' San Diego (member_1 путешествует, возвращается 15 мая) 'Специфика не важна.

Чтобы упростить тестирование Family.location, я хочу заглушить Member.location.Однако мне нужно, чтобы он возвращал два разных (указанных) значения, как в примере выше.В идеале, они должны основываться на атрибуте member, но просто возвращение различных значений в последовательности будет в порядке.Есть ли способ сделать это в RSpec?

Можно переопределить метод Member.location в каждом тестовом примере, например

it "when residence is the same" do 
  class Member
    def location
      return {:residence=>'Home', :work=>'his_work'} if self.male?
      return {:residence=>'Home', :work=>'her_work'}
    end
  end
  @family.location[:residence].should == 'Home'
end

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

Итак, есть ли способ вернуть метод с заглушкой?разные, указанные значения при каждом вызове?

Ответы [ 5 ]

132 голосов
/ 10 мая 2011

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

allow(@family).to receive(:location).and_return('first', 'second', 'other')

Таким образом, при первом вызове @family.location он вернет 'first', во второй раз вернет 'second', и при последующих вызовах он вернет 'other'.

12 голосов
/ 10 сентября 2014

Синтаксис RSpec 3:

allow(@family).to receive(:location).and_return("abcdefg", "bcdefgh")
1 голос
/ 21 ноября 2018

Принятое решение следует использовать, только если у вас есть определенное количество вызовов и вам нужна определенная последовательность данных.Но что, если вы не знаете, сколько звонков будет сделано, или не заботитесь о порядке данных только о том, что каждый раз что-то меняется?Как сказал OP:

будет просто возвращать различные значения в последовательности, и все будет в порядке

Проблема с and_return заключается в том, что возвращаемое значение запоминается.Это означает, что даже если вы вернете что-то динамическое, вы всегда получите одно и то же.

Например,

allow(mock).to receive(:method).and_return(SecureRandom.hex)
mock.method # => 7c01419e102238c6c1bd6cc5a1e25e1b
mock.method # => 7c01419e102238c6c1bd6cc5a1e25e1b

Или практический пример - использование фабрик и получение одинаковых идентификаторов:

allow(Person).to receive(:create).and_return(build_stubbed(:person))
Person.create # => Person(id: 1)
Person.create # => Person(id: 1)

В этих случаях вы можете заглушки тела метода для выполнения кода каждый раз:

allow(Member).to receive(:location) do
  { residence: Faker::Address.city }
end
Member.location # => { residence: 'New York' }
Member.location # => { residence: 'Budapest' }

Обратите внимание, что у вас нет доступа к объекту Member через self в этом контексте, но могут использовать переменные из контекста тестирования.

Например,

member = build(:member)
allow(member).to receive(:location) do
  { residence: Faker::Address.city, work: member.male? 'his_work' : 'her_work' }
end
1 голос
/ 23 января 2017

Если по какой-то причине вы хотите использовать старый синтаксис, вы все равно можете:

@family.stub(:location).and_return('foo', 'bar')
0 голосов
/ 01 сентября 2014

Я попробовал набросок решения здесь выше, но он не работает для моего. Я решил проблему, заглушив замену реализацией.

Что-то вроде:

@family.stub(:location) { rand.to_s }
...