指定明确的’主题’的麻烦?

我正在使用Ruby on Rails 3.0.9和RSpect 2.我试图以下面的方式重构一些spec文件(为了测试更少代码类似的User类对象属性值):

 describe User do let(:user1) { Factory(:user, :users_attribute_a => 'invalid_value') } let(:user2) { Factory(:user, :users_attribute_b => 'invalid_value') } let(:user3) { Factory(:user, :users_attribute_c => 'invalid_value') } it "foreach user" do [ user1, user2, user3 ].each do |user| subject { user } it "should be whatever" user.should_not be_valid ... end end end end 

但是,如果我运行上面的测试,我会收到以下错误:

 Failure/Error: it "should be whatever" do NoMethodError: undefined method `it' for # 

问题是什么? 我怎么解决这个问题?


@Emily回答后更新

如果在上面的代码我使用context "foreach user" do ...而不是it "foreach user" do ...我得到以下错误:

 undefined local variable or method `user1' for # (NameError) 

你正在混合和匹配各种各样的rspec东西。 这是你的东西,修复:

 describe User do let(:user1) { Factory(:user, :users_attribute_a => 'invalid_value') } let(:user2) { Factory(:user, :users_attribute_b => 'invalid_value') } let(:user3) { Factory(:user, :users_attribute_c => 'invalid_value') } it "should not be valid" do [ user1, user2, user3 ].each do |user| user.should_not be_valid end end end 

我会这样做:

 describe User do subject{Factory.build(:user)} it "should not be valid with invalid users_attribute_a" do subject.users_attribute_a = "invalid_value" subject.should_not be_valid end it "should not be valid with invalid users_attribute_b" do subject.users_attribute_b = "invalid_value" subject.should_not be_valid end end 
  • 如果你想要“上下文”,那么很酷,但你不能在你的上下文中的上下文之前有变量。
  • 如果你想要一个规范,那就有一个,但你不能净“it”语句

以最少的可能代码更新

 describe User do it "should not be valid with other attributes" do {:users_attribute_a => 'invalid_value', :users_attribute_b => 'invalid_value', :users_attribute_c => 'invalid_value'}.each do |key, value| Factory.build(:user, key => value).should_not be_valid end end end 

问题是将一个规范嵌套在另一个规范中。 您需要使用context "foreach user"it "foreach user"替换it "foreach user" context "foreach user"

编辑添加:经过一些调查,看起来像设置为let助手只能在it "should ..."块内,而不是在周围的上下文中。 我建议尝试寻找不同的结构解决方案。 最好的解决方案取决于您实际尝试测试的内容。 我猜你要做的是确保用户在删除任何必需属性时无效。 在那种情况下,我所做的是这样的:

 describe User do let(:user_attributes){ Factory.attributes_for(:user) } # Testing missing values aren't valid [:name, :email, :phone].each do |required_attribute| it "should not be valid without #{required_attribute}" do User.new(user_attributes.except(required_attribute)).should_not be_valid end end # Testing invalid values aren't valid [[:email, 'not_an_email'], [:phone, 'not a phone']].each do |(attribute, value)| it "should not be valid with bad value for #{attribute}" do User.new(user_attributes.update(attribute => value)).should_not be_valid end end end 

如果你正在做的事情需要在你正在创建的实例中有更复杂的差异,那么迭代可能没有一种干净的方法。 我不认为DRY在测试中与在代码的其他部分中一样重要。 对于三种用户类型具有三种不同的上下文并且在每种上下文中进行有效性测试没有任何问题。

 describe User do context "with user1" do subject{ Factory(:user, :users_attribute_a => 'invalid_value') } it{ should_not be_valid } end context "with user2" do subject{ Factory(:user, :users_attribute_b => 'invalid_value') } it{ should_not be_valid } end context "with user3" do subject{ Factory(:user, :users_attribute_c => 'invalid_value') } it{ should_not be_valid } end end 

问题是使用“let”设置的帮助程序不在示例上下文之外。

你想要做的是:

 it "does something with all users" do [user1, user2, user3] do |user| user.valid?.should be_true end end 

两种情况都不同

它可能工作的另一种方式(没有尝试过)就像这样:

 context "for all users" do [:user1, :user2, :user3].each do |user| it "does something" do send(user).valid?.should be_true end end end 

这应该工作。 注意如何编写上下文,它将使测试的输出更清晰。 从这样写它就意味着(对我来说)你应该分别对每个属性进行测试,但这是你的选择:

 describe User do let!(:users) { [:users_attribute_a, :users_attribute_b, :users_attribute_c].map do |a| Factory(:user, => 'invalid_value') end } context "Given a user" do context "With an invalid value" do subject { users } it { subject.all?{|user| should_not be_valid } end end end