加速rspec控制器测试:在所有失败之前使用?

时间:2011-10-06 15:34:27

标签: ruby-on-rails rspec rspec2

我有一个简单的控制器测试,包含a.o.以下代码:

context "POST :create" do
  before (:each) do
    post :create, :user_id => @user.id,
         :account => { .. some data ... }
  end
  it { response.status.should == 201 }
  it { response.location.should be_present }
end

现在我想到了一种加速此测试的简单方法,并使用before(:all)代替before(:each)。在这种情况下,帖子只会进行一次。

所以我写道:

context "POST :create" do
  before (:all) do
    post :create, :user_id => @user.id,
         :account => { .. some data ... }
  end
  it { response.status.should == 201 }
  it { response.location.should be_present }
end

但后来我收到以下错误:

 RuntimeError:
   @routes is nil: make sure you set it in your test's setup method.

这是设计的吗?有没有办法规避它?

3 个答案:

答案 0 :(得分:12)

我在rspec邮件列表上问了这个问题,得到了@dchelimsky自己的回复:

  

是。 rspec-rails包含rails的测试框架,其中没有before(:all)概念,因此在每个示例之前重置所有数据。即使我们想在rspec-rails中支持这一点(我也不支持),它首先需要更改rails。

因此在before(:all)中无法进行控制器调用,它只能用于设置数据库或实例变量。

答案 1 :(得分:3)

我不确定这是不是一个好主意,但在||=块中设置before(:each)的类变量似乎有效:

describe PagesController do
  describe "GET 'index'" do
    before(:each) do
      @@response ||= begin
        get :index
        response
      end
    end
    it { @@response.should redirect_to(root_path) }
    it { @@response.status.should == 301 }
    it { @@response.location.should be_present }
  end
end

更新

另一种可能更清晰的方法是在单个规范中使用多个断言。添加:aggregate_failures标记(或将断言包装在aggregate_failures {...}块中)将分别打印每个失败,这提供了单独测试的粒度:

describe PagesController do
  describe "GET 'index'" do
    it "redirects to homepage", :aggregate_failures do
       get :index
       expect(response).to redirect_to(root_path)
       expect(response.status).to eq(301)
       expect(response.location).to be_present
    end
  end
end

答案 2 :(得分:3)

如果你想采用肮脏的全局变量方式并从速度提升中受益,你可以使用它,但警告。这种混乱的逻辑可以完成这项工作,但是却无法通过清晰易读的测试来达到驾驶目的。使用yield进行辅助重构比建议更多。

describe PagesController do
  describe "GET 'index'" do
    before(:each) do
      GLOBAL ||= {}
      @response = GLOBAL[Time.now.to_f] || begin
        get :index
        response
      end
    end
    it { @response.should redirect_to(root_path) }
    it { @response.status.should == 301 }
    it { @response.location.should be_present }
  end
end

您可以在spec / support中输入您选择的文件的重构如下

RSPEC_GLOBAL = {}

def remember_through_each_test_of_current_scope(variable_name)
  self.instance_variable_set("@#{variable_name}", RSPEC_GLOBAL[variable_name] || begin
    yield
  end)
  RSPEC_GLOBAL[variable_name] ||= self.instance_variable_get("@#{variable_name}")
end

因此,测试文件中的代码变为:

describe PagesController do
  describe "GET 'index'" do
    before(:each) do
      remember_through_each_test_of_current_scope('memoized_response') do
        get :index
        response
      end
    end
    it { @memoized_response.should redirect_to(root_path) }
    it { @memoized_response.status.should == 301 }
    it { @memoized_response.location.should be_present }
  end
end

希望它有所帮助,并再次谨慎使用