Имитация/заглушка метода Application Controller с помощью Mocha (с использованием Shoulda, Rails 3)

При написании функциональных тестов для контроллера я столкнулся со сценарием, в котором у меня есть фильтр before_filter, запрашивающий некоторую информацию из базы данных, которая требуется для одного из моих тестов. Я использую Factory_girl для генерации тестовых данных, но я хочу избежать попадания в базу данных, когда это явно не нужно. Я также хотел бы избежать тестирования моего метода before_filter здесь (я планирую протестировать его в отдельном тесте). Насколько я понимаю, насмешка/заглушка - это способ добиться этого.

Мой вопрос заключается в том, как лучше всего имитировать/заглушить этот метод в этом сценарии.

Мой метод перед фильтром ищет сайт в базе данных на основе поддомена, найденного в URL-адресе, и устанавливает переменную экземпляра, которая будет использоваться в контроллере:


#application_controller.rb

def load_site_from_subdomain
  @site = Site.first(:conditions => { :subdomain => request.subdomain })
end

Мой контроллер, использующий этот метод в качестве фильтра before_filter:


# pages_controller.rb

before_filter :load_site_from_subdomain

def show
  @page = @site.pages.find_by_id_or_slug(params[:id]).first
  respond_to do |format|
    format.html { render_themed_template }
    format.xml  { render :xml => @page }
  end
end

Как видите, это зависит от установки переменной @site (с помощью фильтра before_filter). Однако во время тестирования я хотел бы, чтобы тест предполагал, что @site был установлен и что у него есть по крайней мере 1 связанная страница (найденная @site.pages). Затем я хотел бы протестировать свой метод load_site_from_subdomain позже.

Вот что у меня есть в моем тесте (с использованием Shoulda & Mocha):


context "a GET request to the #show action" do

  setup do
    @page = Factory(:page)
    @site = Factory.build(:site)

    # stub out the @page.site method so it doesn't go 
    # looking in the db for the site record, this is
    # used in this test to add a subdomain to the URL
    # when requesting the page
    @page.stubs(:site).returns(@site)

    # this is where I think I should stub the load_site_from_subdomain
    # method, so the @site variable will still be set
    # in the controller. I'm just not sure how to do that.
    @controller.stubs(:load_site_from_subdomain).returns(@site)

    @request.host = "#{ @page.site.subdomain }.example.com"
    get :show, :id => @page.id
  end

  should assign_to(:site)
  should assign_to(:page)
  should respond_with(:success)

end

Это оставляет меня с ошибкой в ​​результатах моего теста, говорящей мне, что @site равно нулю.

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


person Christian    schedule 04.10.2010    source источник


Ответы (2)


Попробуйте заглушить «Site.first», так как это настройка переменной @site, которую вам нужно заглушить, а не возвращаемая переменная из before_filter.

person chris    schedule 04.10.2010
comment
Да я согласен. Предоставление @site (в вашем тесте) путем заглушения вызова Site.first также означает, что вы тестируете действие show с фильтром, работающим должным образом. - person Shadwell; 05.10.2010
comment
Блестящий. Спасибо! Я знал, что это будет что-то простое. Теперь это имеет гораздо больше смысла. - person Christian; 05.10.2010
comment
В контроллере я использовал именованную область, которую назвал find_by_id_or_slug. Теперь кажется, что область видимости не хочет работать (тесты возвращают ошибку отсутствия метода), я переместил ее в метод класса в модели, и она работает нормально. Я в порядке с этим, но есть идеи, почему? - person Christian; 05.10.2010
comment
Я не думаю, что Rails допускает динамические методы с «или». - person chris; 05.10.2010
comment
Нет, это не динамический поиск, это именованная область, я сам определил ее в модели страницы. - person Christian; 05.10.2010

Причина, по которой ваш @site является nil, потому что ваш load_site_from_subdomain выполняет присвоение значения для @site - он не возвращает никакого значения, поэтому ваша заглушка для load_site_from_subdomain просто не присваивает значение @site. Для этого есть два обходных пути:

Первый способ:

Измените load_site_from_subdomain, чтобы просто возвращать значение:

def load_site_from_subdomain
  Site.first(:conditions => { :subdomain => request.subdomain })
end

а затем удалите before_filter :load_site_from_subdomain и измените show на:

def show
  @site = load_site_from_subdomain
  @page = @site.pages.find_by_id_or_slug(params[:id]).first
  respond_to do |format|
    format.html { render_themed_template }
    format.xml  { render :xml => @page }
  end
end

А затем сделать заглушку в тесте:

@controller.stubs(:load_site_from_subdomain).returns(@site)

которые гарантируют, что наш @site заглушен косвенно через load_site_from_subdomain

Второй способ

Чтобы заглушить Site.first, мне не очень нравится этот подход, так как в функциональном тесте нас действительно волнует не то, как извлекается модель, а поведение respond. В любом случае, если вы хотите пойти по этому пути, вы можете заглушить его в своем тесте:

Site.stubs(:first).returns(@site)
person Trung Lê    schedule 24.10.2012