What is the right way to test if the field contains 2 letter string with RSpec ? I am following an old example that I guess worked in rails 2. It creates new Address instance, sets invalid value on it, and then trigger valid? on that instance and finally checks if the errors report something wrong.
it 'requires state to be of length 2' do
subject = Address.new
subject.state = 'Cal'
should_not be_valid
subject.errors.on(:state).should_not be_nil
end
Now, Rails 3 doesn't have errors.on, so I tried with
subject.errors[:state].should_not be_nil
But the problem here is that errors[:attribute] is empty Array instead of nil.
You can still say
subject.errors[:state].should_not be_empty
Validation errors are now in errors.messages
errors.messages.should be_present
Related
I have an issue with my #attributes variable. I would like it to be accessible to keep my code dry, but currently, I have to restate the variable and set it to "values" to get my rspec test to work. What is a better way to do this without duplicating the values.
ref: Unexpected nil variable in RSpec
Shows that it is not accessible in describe, but there needs be another solution. When would "specify" be appropriate? I have not used it.
describe "When one field is missing invalid " do
before(:each) do
#user = create(:user)
#attributes = {"has_car"=>"true", "has_truck"=>"true", "has_boat"=>"true", "color"=>"blue value", "size"=>"large value"}
end
values = {"has_car"=>"true", "has_truck"=>"true", "has_boat"=>"true", "color"=>"blue value", "size"=>"large value"}
values.keys.each do |f|
p = values.except(f)
it "returns invalid when #{f.to_s} is missing" do
cr = CarRegistration::Vehicle.new(#user, p)
cr.valid?
end
end
end
Update based on comments:
I would also like to use the values array hash in other tests. If I put it in the loop as stated, I would still have to repeat it in other places. Any other recommendations?
Update: I tried using let(),
describe "When one field is missing" do
let(:user) {Factorybot.create(:user)}
let(:attributes) = {{"has_car"=>"true", "has_truck"=>"true", "has_boat"=>"true", "color"=>"blue value", "size"=>"large value"}}
attributes do |f|
p = attributes.except(f)
it "returns invalid when #{f.to_s} is missing" do
cr = CarRegistration::Vehicle.new(user, p)
cr.valid?
end
end
end
but get the following error.
attributes is not available on an example group (e.g. a describe or context block). It is only available from within individual examples (e.g. it blocks) or from constructs that run in the scope of an example (e.g. before, let, etc).
In either of your snippets, you don't need attributes inside of your specs. It is data to generate specs. As such, it must live one level above.
describe "When one field is missing" do
let(:user) { Factorybot.create(:user) }
attributes = { "has_car" => "true", "has_truck" => "true", "has_boat" => "true", "color" => "blue value", "size" => "large value" }
attributes do |f|
p = attributes.except(f)
it "returns invalid when #{f.to_s} is missing" do
cr = CarRegistration::Vehicle.new(user, p)
cr.valid?
end
end
end
As you seem to have recognized, based on the other SO post you linked to, you can't refer to your instance variables out in your describe block. Just set it as a local variable as you've done.
Using let
describe "When one field is missing" do
let(:user) {Factorybot.create(:user)}
let(:attributes) = {{"has_car"=>"true", "has_truck"=>"true", "has_boat"=>"true", "color"=>"blue value", "size"=>"large value"}}
## The variables are used INSIDE the it block.
it "returns invalid when a key is missing" do
attributes do |f|
p = attributes.except(f)
cr = CarRegistration::Vehicle.new(user, p)
expect(cr.valid?).to eq(true) # are you testing the expectation? Added this line.
end
end
end
Personally I don't like writing test (like the above) which could fail for multiple reasons. Sergio is correct. But if you want to use let you have to make use of it from WITHIN the it block - this example shows that.
I recently started to test with rspec, so I can strongly be mistaken, correct me if there is a better way
I create two related models
let(:user) {FactoryGirl.create :user}
let!(:participation) {FactoryGirl.create :participation, user: user}
and before one of the tests change one of the related objects
context "when" do
before {participation.prize = 100}
it "" do
binding.pry
end
end
But inside it
participation.prize => 100
user.participatons.select(:prize) => nil
what am I doing wrong ? and how to fix it?
When you say user.participations.select(:prize), you're making a query to the db to get values in the user's participations' prize columns. But when you say before {participation.prize = 100} you're only setting the prize attribute on the participation object. Try saving the participation before the select line:
participation.prize # => 100
participation.save
user.participatons.select(:prize) # => nil
Another possible issue is that user.participations has been memoized by a previous call. Ensure that user.participations.first == participation. If it doesn't, check
1) puts participation.user_id and
2) puts user.participations, user.reload.participations
Lastly, a better way of setting up the test so that you run into this issue less often is something along the lines of:
# let(:price) { 0 } # default price. Optional so that tests won't throw errors if you forget to set it in a context/describe block.
let(:user) {FactoryGirl.create :user}
let!(:participation) {FactoryGirl.create :participation, user: user, price: price}
# ...
context "when ..." do
let(:price) { 100 }
it "" do
binding.pry
end
end
This way, the price is set when you create the model. Following this pattern generally means running into this problem less.
I had the same problem as Nick Rutten in this thread Rails Tutorial: RSpec test decoupling. I suspected the problem was the FactoryGirl.create() saving to the database, and thanks to #prusswan I fixed it.
However I do not understand why these puts don't indicate different User.count.
This is the snippet I'm interested in:
describe "with valid information" do
puts "Number of users pre-Factory:" + User.count.to_s
let(:user){FactoryGirl.create(:user)}
puts "Number of users post-Factory:" + User.count.to_s
(...)
end
I get:
Number of users pre-Factory:0
Number of users post-Factory:0
But shouldn't I get instead?:
Number of users pre-Factory:0
Number of users post-Factory:1
Btw, the factory is defined (although not so relevant now) as:
FactoryGirl.define do
factory :user do
name "Example User"
email "user#example.com"
password "foobar"
password_confirmation "foobar"
end
end
I'm stuck trying to understand why the counter is not increasing, wasn't the problem that it did increase to start with?
thank you
Edit using #derekyau's suggestions:
Right now I've got in this order:
it "print counter before let"
puts "Number of users:" + User.count.to_s
end
let(:user){FactoryGirl.create(:user)}
it "print counter after let"
puts "Number of users:" + User.count.to_s
end
However now I get both values 1:
Number of users:1
Number of users:1
Close but not there yet!
Edit 2
As explained by #Peter Alfvin here: Within a given RSpec describe block, does the order of let, before and it statements matter? . The order of let, before, it is pretty much established, regardless of the code. That, together with #derekyau's explanation closes the question.
Good question, I think that let(:user) is lazily evaluated so it isn't actually called until you use it inside of an it block.
If you want to force it to be evaluated try let!
let!(:user) {FactoryGirl.create(:user)}
If you want to see it change try doing the puts inside of an "it block"
it "check user count" do
puts User.count
end
You should see '1'
I'm using FactoryGirl to create instances of a date dimension model for a Rails-related gem. My factory looks like this:
FactoryGirl.define do
sequence :next_day do |n|
Date.new(2000,12,31) + n.days
end
factory :date_dimension do
the_date = FactoryGirl.generate(:next_day)
date {the_date.to_s}
calendar_year {the_date.strftime("%Y")}
(...other attributes created similarly to calendar_year)
end
end
Out of frustration I actually built a little test to show what's not working:
describe "working date factories" do
before(:all) do
#date_dimension = FactoryGirl.create(:date_dimension)
#jan_two = FactoryGirl.create(:date_dimension)
end
describe "sequence incrementing" do
it "returns a date dimension object ok" do
#date_dimension.date.should == "2001-01-01"
end
it "returns the next date in the sequence" do
#jan_two.date.should == "2001-01-02"
end
end
end
When I run that test, I get:
working date factories
sequence incrementing
returns a date dimension object ok
returns the next date in the sequence (FAILED - 1)
Failures:
1) working date factories sequence incrementing returns the next date in the sequence
Failure/Error: #jan_two.date.should == "2001-01-02"
expected: "2001-01-02"
got: "2001-01-01" (using ==)
I've read a bunch of other questions related to sequences, but it doesn't seem that I'm making the mistakes identified therein. It's a different (likely dumber) mistake. What is it?
I finally found an approach that works, and is probably a little better anyway. I still don't understand why the code above doesn't work - if someone can explain that to me (maybe with a reference to a doc or part of the source code), I'll go ahead and accept that answer - this post is just for those who follow. Here's what worked:
FactoryGirl.define do
factory :date_dimension do
sequence(:date) { |n| (Date.new(2000,12,31) + n.days).to_s }
calendar_year { Date.parse(date).strftime("%Y") }
day_of_week { Date.parse(date).strftime("%A") }
end
end
The code above passes this test:
describe "working date factories" do
before(:all) do
#date_dimension = FactoryGirl.create(:date_dimension)
#jan_two = FactoryGirl.create(:date_dimension)
end
describe "sequences" do
it "returns the proper first date in the sequence" do
#date_dimension.date.should == "2001-01-01"
#date_dimension.calendar_year.should == "2001"
#date_dimension.day_of_week.should == "Monday"
end
it "returns the next date in the sequence" do
#jan_two.date.should == "2001-01-02"
#jan_two.calendar_year.should == "2001"
#jan_two.day_of_week.should == "Tuesday"
end
end
end
I'm trying to assert that the last record did not get deleted in rails model unit test. I raise an exception if the record.count.one? is true. Initially there are two records.
Edited:
There is a user story that says you can delete the users.
You cannot delete the user that you are logged in with. (functional test)
You cannot delete the last user. (unit test)
here it is:
test "verify cannot destroy last user" do
assert_raise(RuntimeError) {
User.find(:all).select {|u| u.destroy} }
assert_equal 1, User.count
end
Here's my literal translation of what you are asking (I think):
last_user = User.last
...
assert_equal last_user, User.last
Here's more traditional test code that is a bit less fragile:
assert_difference('User.count',-1) do
...
end
(But Gutzofter may actually be onto what you're looking for.)