[英]DRY controller specs with RSpec
我目前正在努力保持我的控制器规格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
块是问题所在。 会发生什么事是我的设置数据后实际发生进行了更改get
调用由于上下文嵌套的方式,因此在这方面的例子实际上与最初的方案,而不是一个我想要的工作。
我理解为什么会发生这种情况以及上下文如何嵌套。 我想我想有一些方法来告诉RSpec的想什么,我就往右后的任何运行before
钩子在给定范围内的任何断言前右呢。 这对控制器规格来说是完美的。 我想利用我的控制器的规格,以逐步建立的边缘情况变化筑巢,而无需散射get
通话,甚至打电话到do_get
帮助到每一个我的it
的断言。 这与我正在使用的任何自定义it_should
宏保持同步尤其令人讨厌。
目前RSpec还有什么可以实现这一目标吗? 有什么技巧可以用来接近吗? 它看起来非常适合我看到很多人编写控制器规格的方式; 根据我的发现,人们基本上已经决定在每次断言之前调用do_get
助手。 有没有更好的办法?
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
是一种气味 。
希望这一切都有帮助。
干杯,大卫
将
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
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.