Rails 3 + Rspec 2: Тестирование content_for

Я запускаю Rails 3.1.1, RSpec 2.7.0 и HAML 3.1.3.

Скажем, у меня есть следующие файлы представлений:

приложение/просмотров/макеты/application.html.haml
!!!
%html
  %head
    %title Test
    = stylesheet_link_tag "application"
    = javascript_include_tag "application"
    = csrf_meta_tags

  %body
    = content_for?(:content) ? yield(:content) : yield
приложение/просмотров/макеты/companies.html.haml
- content_for :content do
  #main
    = yield :main
  #sidebar
    = yield :sidebar

= render :template => 'layouts/application'
приложение/просмотров/компании/index.html.haml
- content_for :main do
  %h1 MainHeader
- content_for :sidebar do
  %h1 SidebarHeader

И следующий файл спецификации:

спецификации/мнение/компания/index_spec.rb
require 'spec_helper'

describe 'companies/index.html.haml' do

  it 'should show the headers' do
    render
    rendered.should contain('MainHeader')
    rendered.should contain('SidebarHeader')
  end

end

Когда я запускаю RSpec, я получаю следующую ошибку:

1) companies/index.html.haml should show the headers
   Failure/Error: rendered.should contain('MainHeader')
     expected the following element content to include "MainHeader":
   # ./spec/views/companies/index_spec.rb:7:in `block (2 levels) in <top (required)>'

Сначала я подумал, что RSpec каким-то образом пропускал блоки content_for при визуализации файлов вида. Однако мне не удалось найти какую-либо проблему, связанную с этим в репозитории RSpec github, поэтому я не уверен, кто виноват здесь.

Одно (последнее) решение, которое я нашел, находится в http://www.dixis.com/?p=571. Однако, когда я пробую предлагаемый код

view.instance_variable_get(:@_content_for)

он возвращает nil.

  • Есть ли способ протестировать content_for для просмотра спецификаций?
  • Есть ли лучший способ структурирования моих файлов макета, так что я действительно могу их протестировать и все еще добиваться того же конечного результата?

Ответы

Ответ 1

Из RSpec docs:

Чтобы предоставить макет для рендеринга, вам нужно явно указать как шаблон, так и макет.

Я обновил спецификацию и передал ее:

require 'spec_helper'

describe 'companies/index.html.haml' do

  it 'should show the headers' do
    render :template => 'companies/index', :layout => 'layouts/companies'
    rendered.should contain('MainHeader')
    rendered.should contain('SidebarHeader')
  end

end

Ответ 2

Используя Rspec 2 с Rails 3, чтобы написать спецификации представления для использования content_for, сделайте следующее:

view.content_for(:main).should contain('MainHeader')
# instead of contain() I'd recommend using have_tag (webrat)
# or have_selector (capybara)

p.s. значение блока content_for (...) по умолчанию является пустой строкой, поэтому, если вы хотите напишите спецификации, показывающие случаи, когда content_for (: main) не вызывается, используйте:

view.content_for(:main).should be_blank

Ваша спецификация может быть написана как:

it "should show the headers" do
  render
  view.content_for(:main).should contain('MainHeader')
  view.content_for(:side_header).should contain('SidebarHeader')
end

Таким образом, ваша спецификация показывает, что именно делает ваш просмотр, независимо от любого макета. Для спецификации спецификации я считаю целесообразным протестировать ее изолированно. Всегда полезно писать спецификации вида? Это открытый вопрос.

Вместо этого, если вы хотите написать спецификации, показывающие, как выглядит надпись для пользователя, тогда вам понадобится либо спецификация запроса, либо функция огурца. Третьим вариантом будет спецификация контроллера, которая включает в себя представления.

p.s. если вам нужно специфицировать представление, которое выводит некоторую разметку напрямую и делегирует другую разметку в content_for(), вы можете сделать это следующим образом:

it "should output 'foo' directly, not as a content_for(:other) block" do
   render
   rendered.should contain('foo')
   view.content_for(:other).should_not contain('foo')
end
it "should pass 'bar' to content_for(:other), and not output 'bar' directly" do
   render
   rendered.should_not contain('bar')
   view.content_for(:other).should contain('bar')
end

Это, вероятно, было бы лишним, но я просто хотел показать, как render() заполняет rendered и view.content_for. "rendered" содержит любой вывод, который создает представление напрямую. "view.content_for()" просматривает все содержимое, переданное через content_for().

Ответ 3

Не утруждайте себя просмотром. Их трудно писать хорошо, и они не проверяют достаточно стека, чтобы его можно было использовать (особенно с учетом сложности написания). Вместо этого используйте Cucumber и проверьте свои представления в ходе этого.

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

Если по какой-то нечетной причине вам нужно протестировать content_for, RSpec имеет синтаксис, что что-то вроде body[:content_name] или body.capture :content_name в зависимости от версии (или что-то в этом роде, не использовало ее через некоторое время), Но внимательно изучите, есть ли лучший способ проверить, что вы на самом деле хотите проверить.