RSpec测试控制器与Model.create

时间:2012-08-03 23:13:12

标签: ruby ruby-on-rails-3 rspec ruby-on-rails-3.2

我正在尝试测试控制器以确保只有授权方才能使用RSpec查看正确的子对象。当我收到这个错误时,我无法弄清楚我做错了什么:

ActiveRecord::RecordInvalid: Validation failed: Company can't be blank

我有一个Plan对象和一个Company对象。商店可以有很多计划(想想害虫控制公司)。我想测试一下,鉴于已知的情况,我可以检索公司的计划(假设只有一个)。

计划如下:

class Plan < ActiveRecord::Base
  before_save :default_values

  # Validation
  validates :amount, :presence => true
  validates :company, :presence => true

  # Plans belong to a particular company.
  belongs_to :company, :autosave => true    

  scope :find_all_plans_for_company, lambda {
    |company| where(:company_id => company.id)
  }
  # Other code ...

end 

公司看起来像这样:

class Company < ActiveRecord::Base
  validates :name, :presence => true
  validates :phone1, :presence => true

  validates_format_of :phone1, :phone2,
                      :with => /^[\(\)0-9\- \+\.]{10,20}$/,
                      :message => "Invalid phone number, must be 10 digits. e.g. - 415-555-1212",
                      :allow_blank => true,
                      :allow_nil => true

  has_many :users
  has_many :plans

end

..控制器看起来像这样

def index
    @plans = Plan.find_all_plans_for_company(current_user.company)

    respond_to do |format|
      format.html # index.html.erb
      format.json { render json: @plans }
    end
  end

..而且我的RSpec测试看起来像这样(对不起,如果它充满了噱头,我只是用它来探索并且无法使它工作)。

describe PlansController do

  def valid_attributes
    {
        :company_id => 1,
        :amount => 1000
    }
  end

  describe "GET index" do
    it "should return the Plans for which this users company has" do

      @company = mock_model(Company, :id => 1, :name => "Test Company", :phone1 => "555-121-1212")
      Company.stub(:find).with(@company.id).and_return(@company)

      controller.stub_chain(:current_user, :company).and_return(@company)

      plan = Plan.create! valid_attributes

      get :index, {}
      assigns(:plans).should eq([plan])
    end

    # Other tests ...
  end

end

问题是,当我尝试这个(或者我试过的任何疯狂的其他变种)时,我得到了这个错误:

ActiveRecord::RecordInvalid: Validation failed: Company can't be blank

我不确定为什么会这样,因为我认为Company.stub电话会为我处理这个问题。但显然不是。

我在这里错过了什么,我做错了什么?我怎样才能通过这项测试?

3 个答案:

答案 0 :(得分:2)

让我们剥离这个规范的图层,以确保事情有意义(并确保我理解正在发生的事情)。首先,你在测试什么?

it "should return the Plans for which this users company has" do

  ...

  assigns(:plans).should eq([plan])

因此,您要检查与当前用户的公司关联的计划是否已分配给@plans。我们可以存根或嘲笑其他一切。

查看控制器代码,我们有:

def index
  @plans = Plan.find_all_plans_for_company(current_user.company)

我们需要什么来实现这一点,而不需要访问数据库而不依赖于模型?

首先,我们希望从company中获取模拟current_user.company。这是规范代码中的这两行:

  @company = mock_model(Company, :id => 1, :name => "Test Company", :phone1 => "555-121-1212")
  controller.stub_chain(:current_user, :company).and_return(@company)

这将导致current_user.company返回模拟模型@company。到目前为止一切都很好。

现在进入类方法find_all_plans_for_company。这是我有点困惑的地方。在您的规范中,您在find上存根Company方法,以便@company返回id = 1

但实际上,仅仅在控制器代码中做这样的事情就不够了吗?:

  @plans = current_user.company.plans

如果您这样做,那么在您的测试中,您可以模拟一个计划,然后将其作为模拟公司的plans关联返回:

  @plan = mock_model(Plan)
  @company = mock_model(Company, :plans => [ @plan ])
  controller.stub_chain(:current_user, :company).and_return(@company)

然后分配应该工作,您不需要实际创建任何模型或命中数据库。你甚至不需要给你的模拟公司一个id或任何其他属性,无论如何都与规范无关。

也许我在这里遗漏了一些东西,如果有的话请告诉我。

答案 1 :(得分:1)

为什么需要模拟?

我的标准测试设置是使用Database Cleaner,它可以从测试期间创建的任何记录中清除数据库。通过这种方式,测试使用真实的数据库记录运行,因此在每次测试后都会从测试数据库中删除。

您可能还想看看Factory Girl在测试期间创建模型实例(例如,可以轻松创建10条公司记录)。

请参阅:

答案 2 :(得分:0)

我有三个想法可以解决你的问题:

  1. 尝试将attr_accessible :company_id添加到Plan类。

  2. 因为当您创建一个company_id为1的计划时,mock_model实际上没有保存到数据库,所以它验证失败,因为它不存在于数据库中。

  3. 确保Plan类中的before_save :default_values不会破坏新创建的实例的company_id属性。