Двойная утечка Rspec в другой пример

#ruby #rspec

#ruby #rspec

Вопрос:

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

Мой тест выглядит примерно так:

   describe '#execute' do
    let(:attributes) { {foo: 'bar'} }
    let(:client_double) { double('client', create: nil) }
    let(:use_case) { described.class.new }

    before do
      allow(Client::Base).to receive(:new).and_return(client_double)
      use_case.execute(attributes)
    end

    it 'creates something' do
      expect(Something.find_by(foo: 'bar')).not_to be_nil
    end

    it 'calls client' do
      expect(client).to have_received(:create).with('bar')
    end
  end
 

и первый пример проходит, как и ожидалось, однако rspec продолжает ломаться во втором примере, выдавая мне эту ошибку:

 #<Double "foo"> was originally created in one example but has leaked into another example and can no longer be used. rspec-mocks' doubles are designed to only last for one example, and you need to create a new one in each example you wish to use it for.
 

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

Ответ №1:

Повторное использование приспособлений с помощью методов Let

В данном случае, before на самом деле before(:each) , это повторное использование client_double и атрибутов, которые вы определили с помощью вспомогательного метода #let . let Команды делают эти переменные функционально эквивалентными переменным экземпляра в области описываемого объекта, поэтому вы на самом деле не тестируете свежесозданные объекты в каждом примере.

Некоторые альтернативы включают:

  1. Проведите рефакторинг, чтобы поместить все ваши настройки в before(:each) без операторов let.
  2. Сделайте ваши тесты ВЛАЖНЫМИ, выполнив дополнительные настройки в каждом примере.
  3. Настройте новую область для нового #describe , чтобы ваши тестовые удвоения / значения не использовались повторно.
  4. При необходимости используйте ваши блоки :before , :after или :around для сброса состояния между тестами.

Поскольку вы не показываете фактический класс или реальный тестируемый код, трудно предложить конкретное представление о правильном способе тестирования объекта, который вы пытаетесь протестировать. Даже не ясно, почему вы считаете, что вам нужно протестировать объект collaborator в модульном тестировании, поэтому вы можете подумать и об этом.

Комментарии:

1. Спасибо, Тодд, за действительно хороший ответ, но оказывается, это потому, что клиент был одноэлементным, а я еще не заметил.. поэтому вместо того, чтобы издеваться над методом :new, я издевался над методом:instance, и все заработало.

Ответ №2:

Оказывается, я использовал синглтон в качестве клиента и раньше не осознавал, так что это было действительно кэширование класса с помощью примеров. Чтобы исправить это, все, что я сделал, это издевался над instantiate методом вместо new метода, и все сработало.

Итак, в конце концов это сработало:

 allow(Client::Base).to receive(:instantiate).and_return(client_double)