instance_double Rspec создает периодические сбои спецификации

Я получаю периодические сбои теста при использовании instance_double.

У меня есть файл с 4 спецификациями. Вот источник:

require 'rails_helper'

describe SubmitPost do
  before(:each) do
    @post = instance_double('Post')
    allow(@post).to receive(:submitted_at=)
  end

  context 'on success' do
    before(:each) do
      allow(@post).to receive(:save).and_return(true)

      @result = SubmitPost.call(post: @post)
    end

    it 'should set the submitted_at date' do
      expect(@post).to have_received(:submitted_at=)
    end

    it 'should call save' do
      expect(@post).to have_received(:save)
    end

    it 'should return success' do
      expect(@result.success?).to eq(true)
      expect(@result.failure?).to eq(false)
    end
  end

  context 'on failure' do
    before(:each) do
      allow(@post).to receive(:save).and_return(false)

      @result = SubmitPost.call(post: @post)
    end

    it 'should return failure' do
      expect(@result.success?).to eq(false)
      expect(@result.failure?).to eq(true)
    end
  end

end

Это приложение Rails 4.1.4. Внутри SubmitPost устанавливает submit_at и вызывает save для переданного сообщения. Моя модель Post выглядит так:

class Post < ActiveRecord::Base

  validates :title, presence: true
  validates :summary, presence: true
  validates :url, presence: true
  validates :submitted_at, presence: true

  scope :chronological, -> { order('submitted_at desc') }

end

Это супер ваниль.

Когда я запускаю rake, rspec или bin/rspec, все четыре теста не проходят в 20-30% случаев. Сообщение об ошибке всегда:

Failure/Error: allow(@post).to receive(:submitted_at=)
  Post does not implement: submitted_at=

Если я помечу одну из спецификаций focus: true, эта спецификация не сработает в 100% случаев.

Если я заменю instance_double на double, все спецификации будут успешными в 100% случаев.

Похоже, что instance_double испытывает некоторые трудности с выводом методов, доступных в классе Post. Это также кажется несколько случайным и основанным на времени.

Кто-нибудь сталкивался с этой проблемой? Любые идеи, что может быть не так? Есть смысл, как решить эту проблему? Естественно, вставка точки останова отладки приводит к тому, что спецификации выполняются в 100% случаев.


person EricM    schedule 10.09.2014    source источник
comment
У меня есть еще данные по этому поводу. Корень проблемы в том, что среда Rails не загружается при запуске этой спецификации. Если я запускаю только этот файл, он всегда терпит неудачу. У меня есть в общей сложности четыре файла спецификаций. Если этот файл запускается первым, происходит сбой. Если что-то другое запускается первым, оно завершается успешно. Я предполагаю, что предыдущий запуск загружает среду Rails, поэтому этот тест проходит. Этот тестовый файл находится в spec/interactors, так что это может способствовать возникновению проблемы.   -  person EricM    schedule 10.09.2014
comment
Больше данных. Я проверил, что приложение/интеракторы, в которых живет SubmitPost, находятся в файле await_load_paths. Я также попытался пометить спецификацию типом: :feature. Сообщение об ошибке не изменилось.   -  person EricM    schedule 10.09.2014


Ответы (1)


Проблема, которую вы видите, заключается в том, что ActiveRecord динамически создает методы столбцов. instance_double использует 'Post' для поиска методов, чтобы убедиться, что вы их правильно заглушили (если только класс еще не существует или не был загружен).

Когда предыдущая спецификация загружает модель, ActiveRecord создаст эти динамические методы, поэтому ваша спецификация проходит, поскольку RSpec может затем найти методы (с вызовом respond_to?). При изолированном запуске модель ранее не использовалась, поэтому ActiveRecord еще не создала динамические методы, и ваш тест завершился неудачей, как вы испытываете.

Обходной путь для этого — заставить ActiveRecord загружать динамические методы, когда они вызываются в вашей спецификации:

class Post < ActiveRecord::Base
  def submitted_at=(value)
    super
  end
end

См. документацию RSpec для дальнейшего объяснения и решения проблемы:

https://www.relishapp.com/rspec/rspec-mocks/docs/verifying-doubles/dynamic-classes

person PhilT    schedule 06.10.2014
comment
Спасибо за помощь в решении этой проблемы, я открыл класс в блоке before(:context) do class MyClass < ActiveRecord::Base def my_attribute_setter_or_getter super end end end в своей спецификации и переопределил методы. Но потом я понял, с какой стати мне лезть в эти неприятности, когда я могу просто использовать MyClass.new и заглушить это!!! @EricM, вы можете просто использовать несохраняемый пост и избавить себя от хлопот? то есть @post = Post.new - person ryan2johnson9; 18.02.2015
comment
повторно мой комментарий выше. Я понял, почему double(MyClass) лучше, чем MyClass.new для заглушки. Последний позволит вам заглушить что угодно, поэтому, если вы измените имя метода в коде, который вы заглушаете в спецификации, он не сработает в спецификации, и вы получите ложное срабатывание. использование instance_double вызывает приятную ошибку. Чтобы получить тот же эффект с MyClass.new, вы также должны иметь строку в спецификации, прежде чем заглушить метод, подобный этому expect(my_double).to respond_to('the_method_i_am_about_to_stub') - person ryan2johnson9; 27.11.2015