Rails 3 Factories vs Simple Instantiation - ruby-on-rails

Could someone please explain why Factories are more useful than a simple instantiation during test? More clearly, I do not see the difference between:
before(:each) do
#attr = {
:name => "Example User",
:email => "user#example.com",
:password => "foobar",
:password_confirmation => "foobar"
}
end
it "should create a new instance given valid attributes" do
User.create!(#attr)
end
and this
before(:each) do
#user = Factory(:user)
end
which has the following Factory:
Factory.define :user do |user|
user.name "Michael Hartl"
user.email "mhartl#example.com"
user.password "foobar"
user.password_confirmation "foobar"
end

The bigger your app gets, the more benefits you gain from factories.
Your solution is great for 2-3 models. But lets say you have an article model where you need valid users to test stuff. Now you have 2 files where you define #attr for users. Now imagine there are even more models that need users, like comments, roles, etc. It gets messy.
It is more convenient to use factories. The benefits are you can define multiple default prototypes. Like an admin user, a normal user, an unregistered user etc.
Furthermore the code is DRY, so if you add a new mandatory field you can add it once to your factory and you are done.
So the answer is: Basically they are the same, but the bigger your app gets the more you need a way to manage all your prototypes.

Because it lets you have in a single place all your required variables plus associations.
Moreover, you can easily create stubs or simply extract attributes without additional code.
The interest is clearer once you have several test files as you want to keep your code DRY.
Sidenote:
You should use 'let' instead of creating each time an instance variable

Related

Writing Factories around values that already exist

I've been using Factory Girl to create some basic objects in development when I want to test out an idea, and I commonly run into this:
ActiveRecord::RecordInvalid: Validation failed: Email has already been taken, Login has already been taken
If I've run FactoryGirl.create :user in development mode once and left that user in the db, I'll have to run that command twice if I try to do this again after closing the console. Basically, sequences are getting reset between console instances.
Has anyone come up with a way to write factories such that they'll generate unique results each time? I'm aware that I can use random generators to pick a value from a large domain, minimizing the chance of a collision. I'd like to find a cleaner method, if available, though.
You can write a sequence for this.
Factory.sequence(:email) do |n|
"tester#{n}#example.com"
end
Factory.define :user do |f|
f.name "Tester"
f.email {Factory.next :email}
f.password "tester"
end
Source: about half way down the page.
EDIT
Upon re-reading, it seems that you are trying to create the data in development mode.
You should use the seeds.rb file for this and maintain a counter.
index = User.count || 1
User.create([
{:email => "user#{index++}#example.com",
:password => "password"},
{:email => "user#{index++}#example.com",
:password => "password"}
])

Railstutorial.org Validating presence tests

I've been working through the tutorials at railstutorial.org, and I was a little stumped by the author's code for section -- 6.2.1 Validating presence.
In the user model, the tutorial adds validates :name, :presence => true. Simple enough.
When the author chooses to write the rspec test, he does something that I thought was a little strange.
describe User do
before(:each) do
#attr = { :name => "Example User", :email => "user#example.com" }
end
.
.
.
it "should require a name" do
no_name_user = User.new(#attr.merge(:name => ""))
no_name_user.should_not be_valid
end
end
Why go through the trouble to merge a blank string to #attr when one could get rid of the :each block statement and simply write:
it "should require a name" do
no_name_user = User.new(:name => "", :email => "user#example.com")
no_name_user.should_not be_valid
end
I know that the author uses the #attr variable to validate the presence of the email address as well, which is one indication as to why he used the block statement -- to me it makes more sense to follow the structure of the second block quote. Still, I have a feeling there is something that I'm missing here.
Another explanation that crossed my mind is that it helps to use the #attr structure when there are lots of keys to be entered, as opposed to this rather simplistic case of only name and email.
Anyone have any input?
It's so there's one standard attributes map that can be used across all the tests. When a test requires that a value isn't there, it's removed.
Personally, I wasn't convinced it was worth it as it sort of obfuscates things (as you discovered), but there it is.
The point is to only have code relevant for the test case in the test. The only relevant attribute when testing that the user isn't valid without a name is the name attribute. That test should not have to know anything about the email attribute.
Let's say you add validation for the presence of a new field – you'd have to update every test where you build up a User without that field. With the attr hash at the top, you just pop the new field in there, and all your tests are fine.
Creating objects for testing is a common enough problem that there are many solutions, and plenty of discussion about which way is best. I'd suggest you look into factories. Machinist and FactoryGirl are two alternatives that work great with Rails.

Rails Seed confusion

I'm having trouble seeding my database using seed.rb, specifically where table relationships are concerned.
Here's a sample of the code:
# seed.rb
user = User.find_or_create_by_login(
:login => "myname",
:email => "myname#gmail.com",
:user_type => "Admin",
:password => "admin",
:password_confirmation => "admin")
project = Project.find_or_create_by_user_id(
:user_id => user.id,
:name => "Test Project")
When project is created (along with other unrelated parameters I've left out from above), user_id is empty. How can I get this to work?
This is the strangest behavior I've seen in something so simple. In my seed file, I have about eight tables being created and some are nested 3-4 levels deep (i.e. user has_many projects; projects has_many tasks, etc.).
When I call user user as above and reference user.id multiple times after that, it only works once! I tried adding [user.reload] before each new record is created but to no avail. I don't imagine this will make sense to anyone, but are there any possibilities here? Thanks all.
I figured out what the problem was. The fields that weren't populating were not listed explicitly in attr_accessible in their respective models. The fields listed were being saved correctly.
Thank you very much for your help everyone.
The code is fine and is the correct syntax for find_or_create. As others have said the most likely problem is that the user is invalid. Trying to call user.reload would make it blow up if the user is invalid and so will kind of make the problem more apparent, but the error you'll get from it will be useless (it'll moan about not being able to find a user without an id).
Unfortunately find_or_create doesn't work as a bang method to raise exceptions if it's invalid, so the best thing to do is probably raising an error and outputting the error after attempting to create the user:
user = User.find_or_create_by_login(:login => "myname")
raise "User is invalid: #{user.errors.full_messages}" unless user.valid?
User created with success? if so..try user.reload if not. that is probably the error
Are you sure your user is saved? I think the right syntax for find_or_create_by_XX is Blog.find_or_create_by_title("Some Blog"). If you need to pass more data you need to use find_or_initialize first and set other data after that separately.
Loosely related thread: Rails find_or_create by more than one attribute?
--edit
Passing data as hash to find_or_create_by_XX seems to work too. Docs are under "Dynamic attribute-based finders" here http://apidock.com/rails/v3.0.0/ActiveRecord/Base
try this
use User.find_or_create instead of User.find_or_create_by_login.
It seems like your user object is not saved.
Or before you assign user.id do user.reload
user = User.find_or_create(
:login => "myname",
:email => "myname#gmail.com",
:user_type => "Admin",
:password => "admin",
:password_confirmation => "admin")
[user.reload]
project = Project.find_or_create_by_user_id( :user_id => user.id,
:name => "Test Project")

How do I test that my table design implements my associations properly?

I have written my basic models and defined their associations as well as the migrations to create the associated tables.
EDIT - Adding emphasis to what I specifically want to test.
I want to be able to test:
The associations are configured as intended
The table structures support the associations properly
I've written FG factories for all of my models in anticipation of having a complete set of test data but I can't grasp how to write a spec to test both belongs_to and has_many associations.
For example, given an Organization that has_many Users I want to be able to test that my sample Organization has a reference to my sample User.
Organization_Factory.rb:
Factory.define :boardofrec, :class => 'Organization' do |o|
o.name 'Board of Recreation'
o.address '115 Main Street'
o.city 'Smallville'
o.state 'New Jersey'
o.zip '01929'
end
Factory.define :boardofrec_with_users, :parent => :boardofrec do |o|
o.after_create do |org|
org.users = [Factory.create(:johnny, :organization => org)]
end
end
User_Factory.rb:
Factory.define :johnny, :class => 'User' do |u|
u.name 'Johnny B. Badd'
u.email 'jbadd#gmail.com'
u.password 'password'
u.org_admin true
u.site_admin false
u.association :organization, :factory => :boardofrec
end
Organization_spec.rb:
...
it "should have the user Johnny B. Badd" do
boardofrec_with_users = Factory.create(:boardofrec_with_users)
boardofrec_with_users.users.should include(Factory.create(:johnny))
end
...
This example fails because the Organization.users list and the comparison User :johnny are separate instances of the same Factory.
I realize this doesn't follow the BDD ideas behind what these plugins (FG, rspec) seemed to be geared for but seeing as this is my first rails application I'm uncomfortable moving forward without knowing that I've configured my associations and table structures properly.
Your user factory already creates an organization by virtue of the Factory Girl association method:
it "should associate a user with an organization" do
user = Factory.create(:johnny)
user.organization.name.should == 'Board of Recreation'
organization = user.organization
organization.users.count.should == 1
end
Take a look at 'log/test.log' after running your spec -- you should see an INSERT for both the organization and the user.
If you wanted to test this without the Factory Girl association, make a factory that just creates the user and make the association in the spec:
it "should associate a user with an organization" do
user = Factory.create(:johnny_no_org)
org = Factory.create(:boardofrec)
org.users.should be_empty
org.users << user
org.users.should include(user)
end
Of course all this is doing is testing whether ActiveRecord is doing its job. Since ActiveRecord is already thoroughly tested, you'll want to concentrate on testing the functionality of your application, once you've convinced yourself that the framework actually does what it's supposed to do.

Should native validations be tested in rails?

Everybody knows that automated testing is a good thing.
Not everybody knows exacly what to test.
My question is if native validations like validate_presence_of, validate_uniqueness_of and so on should be tested in the application.
In my office we are three, one thinks it should be tested, one thinks it shouldn´t and I am up in the air.
Yes.
Testing that a model attribute is present or not is only testing the validates_presence_of code as a by-product of the real test which is that the validates_presence_of exists within your model.
If someone commented out a bunch of validation code and then forgot to uncomment it then this would go undetected and could cause all sorts of problems.
I test them, not because I think they don't work but to ensure that they are present in my model when required.
Are you planning to write unit tests for every single Ruby operator and API method as well?
Your unit tests should test your own code, not other people's - that's their job, why duplicate their work? And if you don't trust them to do their job well, why are you using their code?
Matthew Bass has a great gem he's released for just this type of thing. It adds rspec matchers that check to make sure the validation is in place without actually running the underlying ActiveRecord code. Read more about it here.
It adds matchers for validations:
it_should_validate_presence_of :first_name, :last_name, :email
it_should_validate_numericality_of :zip
it_should_validate_uniqueness_of :email
it_should_validate_inclusion_of :gender, :in => %w(Male Female)
Also matchers for associations:
it_should_belong_to :employer
it_should_have_many :friends, :romans, :countrymen
it_should_have_one :account
it_should_have_and_belong_to_many :comments
And a few other useful additions:
# tests that User.count increases by 1
it_should_be_createable :with => {:first_name => 'Ed', :last_name => 'The Duck', :email => 'a#b.com'}
# tests that the attribute is protected
it_should_protect :email
That's not by any means an exhaustive list. I've got a fork where I've added a few others I needed, likely there are others floating around as well. It's a good approach and for me fit the middle ground between ensuring the validations were still in the model, and having to explicitly write tests to execute ActiveRecord code to ensure it.
This is where tools like Shoulda really come in handy. I think its totally up to you to test how you write the code with the tools people provide you. Just because you are using has_many, does not mean you are using it right!
Seriously, if you integrate Shoulda into the mix, it becomes trivial to test these sorts of things. if you run rcov, its going to tell you all the code you wrote is not fully tested, unless you do.
Test the code you write. ActiveRecord has great test coverage including coverage for the validation class methods.
Spec:
require 'spec_helper'
describe User do
before(:each) do
#user = User.new
end
it "should not be valid without an email" do
#user.save.should be_false
#user.should_not be_valid
#user.email = "example#example.com"
#user.should be_valid
#user.save.should be_true
end
end
To get that spec to pass you would need
class User < ActiveRecord::Base
validates_presence_of :email
end

Resources