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 и извиква запазване на предадената публикация. Моят пост модел изглежда така:

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, е в eager_load_paths. Опитах също да маркирам спецификацията с type: :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