带RSpec的DRY控制器规格

Chr*_*ent 6 rspec ruby-on-rails dry functional-testing

我目前正在努力保持我的控制器规格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
Run Code Online (Sandbox Code Playgroud)

显然这是一个人为的例子,但它说明了我想做什么和什么不行.主要是,before"未发布"上下文中的块是问题所在.由于上下文嵌套的方式,我在调用实际发生的设置数据的变化会发生什么变化get,因此该上下文中的示例实际上是使用初始场景而不是我想要的场景.

我理解为什么会发生这种情况以及上下文如何嵌套.我想我会喜欢有一些方法来告诉RSpec的想什么,我就向右运行后,任何before尚未右勾拳之前给定的范围内的任何断言.这对控制器规格来说是完美的.我想利用我的控制器规范中的嵌套来逐渐构建边缘情况的变体,而不必将get调用分散,甚至do_get在我的每个it断言中调用助手.这与it_should我正在使用的任何自定义宏保持同步尤其令人讨厌.

目前RSpec还有什么可以实现这一目标吗?有什么技巧可以用来接近吗?它看起来非常适合我看到很多人编写控制器规格的方式; 根据我的发现,人们基本上已经决定do_get在每次断言之前都要求帮助者.有没有更好的办法?

Dav*_*sky 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
Run Code Online (Sandbox Code Playgroud)

现在,重复的唯一"知识"是示例的名称,这些名称可以由每个示例末尾的匹配器生成.这可以通过使用该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
Run Code Online (Sandbox Code Playgroud)

那里.干.并且非常易读且易于更改.现在,当您碰巧为任一上下文添加更多示例时,您可以添加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
Run Code Online (Sandbox Code Playgroud)

现在唯一重复的代码(与DRY​​原则不同)是get.如果你真的对此感到强烈,你可以将这些调用委托给类似get_show(id)或类似的方法,但那时并没有真正购买太多.它不像API get会从你的下面改变,唯一的参数getitem你的id,你在实例中真正关心的(所以没有不必要的信息).

至于使用subject捕获响应并从交易中获得单行,这只会让事情变得非常难以阅读并且不会为您节省太多.事实上,我开始考虑subject以这种方式使用气味.

希望这一切都有帮助.

干杯,大卫