Как заглушить метод для переменной экземпляра?

Итак, у меня есть этот простой рубиновый класс:

class GetRequestList
  def initialize(current_user, filter_hash)
    @authorizer = RequestAuthorizer.new(current_user)
    @filter    = RequestFilter.new(filter_hash)
  end
  def generate
    Request.send_chain(@authorizer.method_chain)
           .send_chain(@filter.method_chain)
  end
end

И я хочу проверить, что Request получает два метода send_chain отдельно от реализаций RequestAuthorizer и RequestFilter. Для этого я пытаюсь использовать некоторые заглушки:

require 'test_helper'

class GetRequestListTest < ActiveSupport::TestCase

  test "request should be filtered by filter and role" do
    Request.expects(:send_chain).twice.returns([build(:request)])
    RequestFilter.stubs(:new)
    RequestFilter.any_instance.stubs(:method_chain).returns([])
    RequestAuthorizer.stubs(:new)
    RequestAuthorizer.any_instance.stubs(:method_chain).returns([])
    assert GetRequestList.new(:current_user, :filter).generate.size == 1
  end
end

Вы видите, что не так. stubs(:new) возвращает nil, и в переменных экземпляра GetRequestList нет экземпляров RequestAuthorizer и RequestFilter, и мы получаем ошибку. Я не могу понять, как заглушить методы для переменных экземпляра. Какие-либо предложения?


person lompy    schedule 30.06.2013    source источник


Ответы (3)


Вместо того, чтобы заглушать new, чтобы не возвращать значения, пусть он возвращает что-то, например.

mock_request_filter = mock()
RequestFilter.stubs(:new).returns(mock_filter)

Это также позволяет вам читать заглушки на any_instance — просто вместо этого установите их на mock_request_filter.

person Frederick Cheung    schedule 30.06.2013
comment
Спасибо за быстрый ответ. Решено с помощью method_chain_mocker = mock(method_chain: []), но я тоже попробую ответить Исмаэлю. - person lompy; 30.06.2013

Вот почему вы должны заключать переменные экземпляра в методы. Взгляните на этот подход. Таким образом, ваши тесты не знают о RequestFilter или RequestAuthorizer. И теперь вы также активны тем, что получаете их как параметры. Обратите внимание, что я также обернул инициализацию авторизатора и фильтра внутри метода. Вы также можете обернуть оба внутри другого, если ваш основной метод инициализатора получает больше материала.

class GetRequestList
  def initialize(current_user, filter_hash)
    initialize_authorizer
    initialize_filter
  end

  def generate
    Request.send_chain(authorizer_method_chain)
           .send_chain(filter_method_chain)
  end

  private

  def initialize_authorizer
    @authorizer = RequestAuthorizer.new(current_user)
  end

  def initialize_filter
    @filter = RequestFilter.new(filter_hash)
  end

  def authorizer_method_chain
    @authorizer.method_chain
  end

  def filter_method_chain
    @filter.method_chain
  end
end

и тест

require 'test_helper'

class GetRequestListTest < ActiveSupport::TestCase

  test "request should be filtered by filter and role" do
    get_request_list = GetRequestList.new(:current_user, :filter)
    get_request_list.stubs(:initialize_authorizer)
    get_request_list.stubs(:initialize_filter)

    get_request_list.stubs(:authorizer_method_chain).returns(:authorizer_method_chain)
    get_request_list.stubs(:filter_method_chain).returns(:filter_method_chain)

    Request.expects(:send_chain).with(:authorizer_method_chain).returns([build(:request)])
    Request.expects(:send_chain).with(:filter_method_chain).returns([build(:request)])

    assert get_request_list.generate.size == 1
  end
end

Я также использовал символ для замены объектов авторизации и фильтрации, потому что вам даже не нужно, чтобы они были моками. Это также может быть что-то другое, например 1 и 2, но сохранение символов или строк позволяет вам правильно называть вещи.

person Ismael    schedule 30.06.2013
comment
Ваше решение должно передать current_user и filter_hash новым инициализаторам. В любом случае, я придерживаюсь этой идеи «метод делает одно дело», но не слишком ли много строк, чтобы просто присваивать переменные и вызывать для них методы? И почему мои тесты не должны знать об этих объектах, если это суть класса GetRequestList: инициализировать эти два и вызывать методы для них? - person lompy; 30.06.2013
comment
Также обнаружил, что Request ожидает только один вызов send_chain, так как он возвращает Array, моя ошибка. - person lompy; 01.07.2013
comment
Вы хотите, чтобы ваши модульные тесты были сосредоточены на самых маленьких возможностях. Таким образом, вы не хотите раздувать их знаниями об именах других классов. Хранение этих знаний внутри метода кажется хорошим решением. - person Ismael; 02.07.2013
comment
О методе инициализации. Да, вы можете оставить его таким, какой он у вас есть, вместо того, чтобы извлекать его другими методами, это полностью зависит от вас. - person Ismael; 02.07.2013

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

GetRequestList.new(:current_user, :filter).tap do |it|
  def it.authorizer
    @authorizer
  end
  install_stubs(it.authorizer)
end.generate
person Richard Wan    schedule 10.04.2015