ruby-on-rails - 带有 RSpec 的 DRY Controller 规范

标签 ruby-on-rails rspec dry functional-testing

我目前正在努力使我的 Controller 规范保持干燥和简洁,并减少每个示例的一个断言。我遇到了一些困难,尤其是在嵌套结构中放置实际 Controller 请求调用以匹配各种边缘情况的位置。

这是一个示例,简化以演示问题:

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 之后立即运行什么。在给定上下文中的任何断言之前 Hook 。这对于 Controller 规范来说是完美的。我想利用我的 Controller 规范中的嵌套来逐渐建立边缘情况的变化,而不必分散 get打电话,甚至调用 do_get我的每个 it 的助手断言。与任何自定义 it_should 保持同步尤其令人讨厌。我正在使用的宏。

RSpec 目前有什么东西可以做到这一点吗?有什么技巧可以用来接近吗?它似乎非常适合我看到很多人编写 Controller 规范的方式;根据我的发现,人们基本上已经接受了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 .如果你真的对此有强烈的感觉,你可以将这些调用委托(delegate)给像 get_show(id) 这样的方法。或类似的东西,但那时它并没有真正购买太多。它不像 get 的 API将从你下面改变,唯一的论点是getitem的 id,您在示例中实际关心的 id(因此没有不必要的信息)。

至于使用subject捕捉响应并让单行退出交易,这只会让事情变得非常难以阅读并且不会为您节省太多。事实上,我已经开始考虑使用 subject这样to be a smell .

希望这一切都有帮助。

干杯,
大卫

关于ruby-on-rails - 带有 RSpec 的 DRY Controller 规范,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9267187/

相关文章:

ruby-on-rails - 当以不同的方式编写时,范围解析在 ruby​​ 中的工作方式不同

ruby-on-rails - 如何使 Rails 中的 Urls seofriendly

javascript - 将对象数组作为参数传递给 Rails Controller

ruby-on-rails-3 - 如何使用 RSpec 在 after_save 回调中测试类初始化?

ruby - 打印到标准输出的单元测试顺序

ruby-on-rails-3 - 如何在 Capybara 验收测试中 stub Controller /current_user 方法

coffeescript - 如何 DRY coffeescript

c++ - 根据可调用的签名自动选择一元与二进制 std::transform 函数重载

c++ - 函数的两个变体,中间有一个简单的 if 语句

javascript - 表单多次提交