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
cl开发者_JS百科ass 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)
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
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
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
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
精彩评论