我正在尝试测试控制器以确保只有授权方才能使用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
电话会为我处理这个问题。但显然不是。
我在这里错过了什么,我做错了什么?我怎样才能通过这项测试?
答案 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)
我有三个想法可以解决你的问题:
尝试将attr_accessible :company_id
添加到Plan类。
因为当您创建一个company_id为1的计划时,mock_model实际上没有保存到数据库,所以它验证失败,因为它不存在于数据库中。
确保Plan类中的before_save :default_values
不会破坏新创建的实例的company_id属性。