Код модульного тестирования, который ссылается на модели Rails без загрузки моделей - PullRequest
0 голосов
/ 26 января 2019

Я пытаюсь выполнить модульное тестирование простого старого объекта Ruby, у которого есть метод, вызывающий метод класса в модели Rails.Приложение Rails довольно большое (для загрузки 10 секунд), поэтому я бы предпочел не загружать все Rails для выполнения моего модульного теста, который должен выполняться менее чем за 1 секунду.

Пример:

class Foo
  def bar
    SomeRailsModel.quxo(3)
  end
end

RSpec.describe Foo do
  let(:instance) { Foo.new }

  it 'calls quxo on SomeRailsModel' do
    expect(SomeRailsModel).to receive(:quxo)
    instance.bar
  end
end

Проблема здесь в том, что мне нужно require 'rails_helper', чтобы загрузить Rails, чтобы app/models/some_rails_model был доступен.Это приводит к медленным юнит-тестам из-за зависимости от Rails.

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

Пример:

RSpec.describe Foo do
  let(:instance) { Foo.new }
  SomeRailsModel = Object.new unless Kernel.const_defined?(:SomeRailsModel)

  it 'calls quxo on SomeRailsModel' do
    expect(SomeRailsModel).to receive(:quxo)
    instance.bar
  end
end

Этот код позволяет мне избежать загрузки всех Rails и выполняется очень быстро.К сожалению, по умолчанию (и мне это нравится) RSpec рассматривает константу как частичное удвоение и жалуется, что моя SomeRailsModel константа не отвечает на сообщение quxo.Удостоверяющие пары хороши, и я хотел бы сохранить этот ремень безопасности.Я могу по отдельности отключить проверку, поместив ее в специальный блок, определенный RSpec.

Наконец, вопрос.Каков рекомендуемый способ проведения быстрых модульных тестов на PORO, которые используют модели Rails, не требуя наличия всех Rails, и в то же время сохраняя включенную функцию проверки двойников?Есть ли способ создать «тонкий» rails_helper, который может просто загрузить app/models и минимальное подмножество ActiveRecord, чтобы проверка работала?

1 Ответ

0 голосов
/ 28 января 2019

После обсуждения нескольких идей с коллегами, вот решение консенсуса:

class Foo
  def bar
    SomeRailsModel.quxo(3)
  end
end

require 'spec_helper' # all we need!

RSpec.describe Foo do
  let(:instance) { Foo.new }
  let(:stubbed_model) do
    unless Kernel.const_defined?("::SomeRailsModel")
      Class.new { def self.quxo(*); end }
    else
      SomeRailsModel
    end
  end

  before { stub_const("SomeRailsModel", stubbed_model) }

  it 'calls quxo on SomeRailsModel' do
    expect(stubbed_model).to receive(:quxo)
    instance.bar
  end
end

При локальном запуске мы проверим, определен ли уже класс модели.Если это так, используйте его , поскольку мы уже заплатили цену за загрузку этого файла.Если это не так, создайте анонимный класс, который реализует тестируемый интерфейс.Используйте stub_const для заглушки либо в анонимном классе, либо в реальной сделке.

Для локальных тестов это будет очень быстро.Для тестов, выполняемых на CI-сервере, мы обнаружим, что модель уже загружена, и преимущественно используем ее.Мы также получаем автоматическую проверку двойного метода во всех случаях.

Если реальный интерфейс модели Rails изменяется, но анонимный класс отстает, прогон CI его перехватит (или интеграционный тест его перехватит).

ОБНОВЛЕНИЕ: Вероятно, мы немного высушим это вспомогательным методом в spec_helper.rb.Например:

def model_const_stub(name, &blk)
  klass = unless Kernel.const_defined?('::' + name.to_s)
            Class.new(&blk)
          else
            Kernel.const_get(name.to_s)
          end

  stub_const(name.to_s, klass)
  klass
end

# DRYer!
let(:model) do
  model_const_stub('SomeRailsModel') do
    def self.quxo(*); end
  end
end

Возможно, не окончательная версия, но это дает представление о нашем направлении.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...