Сухие спецификации контроллера с 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 ответа:
Сухой принцип гласит ,что " каждая часть знания должна иметь единственное, однозначное, авторитетное представление в системе.- То, что вы делаете, гораздо больше касается сохранения нескольких персонажей здесь и там, чем сохранения вещей сухими, и в результате получается запутанная сеть зависимостей вверх и вниз по иерархии, которая, как вы можете видеть, сука, чтобы заставить ее делать то, что вы хотите, и, следовательно, хрупкая и хрупкая.
Давайте начнем с того, что вы написали таким образом, что это многословно и работает:
Теперь единственными" фрагментами знания", которые дублируются, являются имена примеров, которые могут быть сгенерированы сопоставителями в конце каждого примера. Это можно решить читаемым способом с помощью метода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