【问题标题】:Trouble on specifying explicit 'subject's?指定明确的“主题”有问题吗?
【发布时间】:2011-09-19 18:12:55
【问题描述】:

我正在使用 Ruby on Rails 3.0.9 和 RSpect 2。我正在尝试通过以下方式重构一些规范文件(以便使用更少的代码进行类似 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 #<RSpec::Core::ExampleGroup::Nested_1::Nested_2::Nested_2:0x00000106ccee60>

有什么问题?我该如何解决?


@Emily 回答之后

UPDATE

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

undefined local variable or method `user1' for #<Class:0x00000105310758> (NameError)

【问题讨论】:

  • 仅供参考,这里使用subject 是一个红鲱鱼。您从未真正使用过您在示例中设置的主题。

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


【解决方案1】:

问题在于一个规范嵌套在另一个规范中。您需要将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

【讨论】:

    【解决方案2】:

    您正在混合和匹配各种 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
    
    • 如果您想拥有“上下文”,那很酷,但是您的上下文中的上下文之前不能有变量。
    • 如果你想有一个规范,那就有一个,但你不能净“它”陈述

    用最少的代码更新

    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
    

    【讨论】:

    • 有什么方法可以重构第二个代码块中的代码以减少代码编写量?
    • 您可以使用let将变量过滤到上下文中
    【解决方案3】:

    问题是用“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
    

    【讨论】:

      【解决方案4】:

      这应该可行。注意上下文是如何编写的,它将使测试的输出更清晰。以这种方式编写它意味着(对我而言)您应该分别对每个属性进行测试,但这是您的选择:

      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
      

      【讨论】:

        猜你喜欢
        • 2020-03-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-06-08
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多