我目前正在努力保持我的控制器规格DRY和简洁,并在每个示例下降为一个断言。我遇到了一些困难,特别是在嵌套的结构中将实际的控制器请求调用放在哪里以匹配各种边缘情况。
以下是一个示例,简化为演示问题:
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
块是问题所在。由于上下文嵌套的方式,我在<{em> get
调用之后实际发生了对设置数据的更改所发生的变化,因此该上下文中的示例实际上是在处理初始场景而不是我想要的那个。
我理解为什么会发生这种情况以及上下文如何嵌套。我想我喜欢有什么方法告诉RSpec我希望它 任何before
挂钩之后 >在给定上下文中的任何断言之前。这对控制器规格来说是完美的。我想利用我的控制器规范中的嵌套来逐渐构建边缘案例的变体,而不必将get
调用或甚至调用do_get
助手分散到我的每个it
助手中。 1}}断言。与我正在使用的任何自定义it_should
宏保持同步尤其令人讨厌。
目前RSpec有什么可以实现这一目标吗?有什么技巧可以用来接近吗?它看起来非常适合我看到很多人编写控制器规格的方式;根据我的发现,人们基本上已经决定在每次断言之前调用do_get
助手。还有更好的方法吗?
答案 0 :(得分:6)
DRY原则指出“每一条知识都必须在系统内具有单一,明确,权威的表现形式。”你正在做的更多是关于在这里和那里保存一些字符,而不是保持干燥,结果是一个层层叠叠的网络上下层,正如你所看到的,是一个婊子去做什么你想要它,因此脆弱和脆弱。
让我们从你用一种冗长而有效的方式写出的内容开始:
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
现在唯一重复的代码(与DRY原则不同)是get
。如果你真的对此感到强烈,你可以将这些调用委托给像get_show(id)
这样的方法,或者其他一些方法,但那时候并没有真正买得多。它不像get
的API会从你的下方改变,get
的唯一参数是item
的id,你在实例中真正关心的是(所以没有不必要的信息。)
至于使用subject
来捕获响应并让单行者退出交易,这只会让事情变得非常困难并且不会为您节省太多。事实上,我开始考虑以这种方式使用subject
to be a smell。
希望这一切都有所帮助。
干杯, 大卫
答案 1 :(得分:3)
威尔
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