Trouble on specifying explicit 'subject's?

烈酒焚心 提交于 2019-12-11 04:37:55

问题


I am using Ruby on Rails 3.0.9 and RSpect 2. I am trying to refactoring some spec file in the following way (in order to test with less code similar User class object attribute values):

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

However, if I run the above test I get the following error:

Failure/Error: it "should be whatever" do
  NoMethodError:
    undefined method `it' for #<RSpec::Core::ExampleGroup::Nested_1::Nested_2::Nested_2:0x00000106ccee60>

What is the problem? How can I solve that?


UPDATE after the @Emily answer.

If in the above code I use context "foreach user" do ... instead of it "foreach user" do ... I get the following error:

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

回答1:


You're mixing and matching all sorts of rspec stuff. Here's your stuff, fixed:

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

I would do it this way:

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
  • If you want to have "context", then cool, but you can't have variables before your context inside of your context.
  • If you want to have a specification, then have one, but you can't net "it" statements

UPDATE WITH LEAST POSSIBLE CODE

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



回答2:


The problem is having one spec nested within another. You need to replace it "foreach user" with context "foreach user".

Edited to add: After some investigation, it looks like helpers set with let are only available inside of the it "should ..." block, and not in the surrounding context. I'd recommend is trying to find a different structural solution. What the best solution is will depend on what you're actually trying to test. I'm guessing what you're trying to do is make sure the user is invalid when you remove any of the required attributes. In that case, what I've done is something like this:

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

If you're doing something that requires more complex differences in the instance you're creating, there may not be a clean way to do it with iteration. I don't think DRY is quite as essential in testing as it is in other parts of your code. There's nothing wrong with having three different contexts for the three user types, and a validity test in each context.

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



回答3:


The problem is that the helpers that are set with "let" do not exist outside of a example context.

What you're trying to do could be achieved as:

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

Both contexts are different

Another way it might work (haven't tried it) it's like this:

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:


This should work. Note how the context is written, it will make the output of tests clearer. From writing it this way it implies (to me) that you should make a test for each attribute separately, but it's your choice:

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


来源:https://stackoverflow.com/questions/7475386/trouble-on-specifying-explicit-subjects

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!