СУХОЙ контроллер со спецификациями RSpec - PullRequest
6 голосов
/ 14 февраля 2012

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

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

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 голосов
/ 19 июня 2012

Принцип СУХОГО гласит: «Каждое знание должно иметь одно, однозначное, авторитетное представление в системе».То, что вы делаете, - это гораздо больше о сохранении нескольких символов здесь и там, чем о сохранении СУХОГО, и в результате получается запутанная паутина зависимостей вверх и вниз по иерархии, которая, как вы можете видеть, является сукой, чтобы сделать то, чтовы хотите, и, следовательно, хрупкие и хрупкие.

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

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

Там.DRY.И вполне читабельно и легко изменить.Теперь, когда вам случится добавить больше примеров для любого из контекстов, вы можете добавить 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

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

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

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

Приветствия, Дэвид

3 голосов
/ 18 февраля 2012

Будет ли

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
...