Сухие спецификации контроллера с RSpec


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

Вот пример, упрощенный для демонстрации проблемы:

describe MyController do
  let(:item) { Factory(:item) }
  subject { response }

  describe "GET #show" do
    before(:each) do
      get :show
    end

    context "published item" do
      it { should redirect_to(success_url) }
    end

    context "unpublished item" do
      before(:each) do
        item.update_attribute(published: false)
      end

      it { should redirect_to(error_url) }
    end
  end
end
Очевидно, что это надуманный пример, но он иллюстрирует, что я хотел бы сделать, а что не работает. Главным образом, before блок под" неопубликованным " контекстом-это проблема. То, что происходит, - это изменение, которое я внес в установочные данные, на самом деле происходит после вызова get из-за того, как контексты вложены, поэтому пример в этом контексте фактически работает с начальным сценарием, а не с тем, который я намереваюсь. Я понимаю, почему это происходит и как складываются контексты. Я думаю, что я хотел бы иметь какой-то способ сказать RSpec, что я хотел бы, чтобы он работал правильно после любого before крючки еще правыперед любыми утверждениями в данном контексте. Это было бы идеально для спецификаций контроллера. Я хотел бы воспользоваться преимуществом вложенности в мои спецификации контроллера, чтобы постепенно создавать вариации крайних случаев без необходимости разбрасывать вызов get или даже вызов помощника do_get в каждое из моих утверждений it. Это особенно раздражает, чтобы синхронизировать с любыми пользовательскими макросами it_should, которые я использую.

Есть ли что-нибудь в RSpec в настоящее время, чтобы выполнить вот это? Есть ли какие-нибудь уловки, которые я могу использовать, чтобы подобраться ближе? Казалось бы, это идеально подходит к тому, как я видел, что многие люди пишут свои спецификации контроллеров; из того, что я обнаружил, люди в основном соглашаются на то, что помощники do_get вызываются перед каждым утверждением. Есть ли лучший способ?

2 6

2 ответа:

Сухой принцип гласит ,что " каждая часть знания должна иметь единственное, однозначное, авторитетное представление в системе.- То, что вы делаете, гораздо больше касается сохранения нескольких персонажей здесь и там, чем сохранения вещей сухими, и в результате получается запутанная сеть зависимостей вверх и вниз по иерархии, которая, как вы можете видеть, сука, чтобы заставить ее делать то, что вы хотите, и, следовательно, хрупкая и хрупкая.

Давайте начнем с того, что вы написали таким образом, что это многословно и работает:

describe MyController do
  describe "GET #show" do
    context "published item" do
      it "redirects to the success url" do
        item = Factory(:item, published: true)
        get :show, :id => item.id
        response.should redirect_to success_url
      end
    end

    context "unpublished item" do
      it "redirects to the error url" do
        item = Factory(:item, published: false)
        get :show, :id => item.id
        response.should redirect_to error_url
      end
    end
  end
end
Теперь единственными" фрагментами знания", которые дублируются, являются имена примеров, которые могут быть сгенерированы сопоставителями в конце каждого примера. Это можно решить читаемым способом с помощью метода example, который является псевдонимом it:
describe MyController do
  describe "GET #show" do
    context "published item" do
      example do
        item = Factory(:item, published: true)
        get :show, :id => item.id
        response.should redirect_to success_url
      end
    end

    context "unpublished item" do
      example do
        item = Factory(:item, published: false)
        get :show, :id => item.id
        response.should redirect_to error_url
      end
    end
  end
end

Вот. СУХОЙ. И вполне читаемый и легко изменяемый. Теперь, когда вы добавляете больше примеров для любого из контекстов, вы можете добавить let:

describe MyController do
  describe "GET #show" do
    context "published item" do
      let(:item) { Factory(:item, published: true) }
      example do
        get :show, :id => item.id
        response.should redirect_to success_url
      end

      example do
        # other example
      end
    end
    # ...
  end
end

Теперь единственный дублированный код (не то же самое, что сухой принцип) - это get. Если вы действительно сильно чувствуете это, вы можете делегировать эти вызовы методу, такому как get_show(id) или что-то подобное, но на самом деле это не очень много покупает в тот момент. Это не похоже на то, что API для get изменится из-под вас, и единственным аргументом для get является идентификатор item, который вам действительно важен в Примере (поэтому нет ненужной информации).

Что касается использования subject для захвата ответа и получения однобокие из сделки, это просто делает вещи действительно трудными для чтения и не спасает вас много. На самом деле, я пришел к мысли использовать subject таким образом , чтобы быть запахом.

Надеюсь, это все поможет.

Ура!, Дэвид

Воля

context "unpublished item" do
  let(:item) do
    Factory(:item, published: false)
  end

  it { should redirect_to(error_url) }
end

Работать на вас? Кстати, before по умолчанию является before(:each), так что вы можете высушить ваши спецификации немного больше.

Обновление: вы также можете изолировать примеры с анонимными контекстами, например:

describe "GET #show" do
  let(:show!) do
    get :show
  end

  context do
    before { show! }

    context "published item" do
      it { should redirect_to(success_url) }
    end 

    # another examples with show-before-each
  end

  context "unpublished item" do
    before do
      item.update_attribute(published: false)
      show!
    end

    it { should redirect_to(error_url) }
  end
end