I am using the following code to generate an email address:
sequence :email do |n|
"person#{n}#example.com"
end
Then to generate a user, I use the following code:
factory :user do
sequence(:username) {|n| "person#{n}"}
email { generate :email }
password 'password'
password_confirmation { |u| u.password }
end
However, when I run the test, it always generates "person1#example.com". It never increases to '2'.
I get the error message "person1#example.com" already exists in the database.
How do I get FactoryGirl to increment up?
If you get this message when generating users within single test, then yes, it's the problem with generating unique emails. But you can receive that kind of error message because of test database not being clean (user left over from a former test). If in doubt, check test log.
Related
I'm relatively new to testing and very new to Rails 4 and rSpec. I am trying to test a controller that uses Devise for authentication and I am stuck. All of the examples I can find are for Rails 3.
I'm using Rails 4.0.3, Devise 3.2.3, rSpec 2.14.1 and FactoryGirl 4.4.0.
class LessonPlansController < ApplicationController
before_action :authenticate_user!
# GET /lesson_plans
def index
#lesson_plans = current_user.lesson_plans.to_a
end
.
.
.
private
# Use callbacks to share common setup or constraints between actions.
def set_lesson_plan
#lesson_plan = LessonPlan.find(params[:id])
end
# Only allow a trusted parameter "white list" through.
def lesson_plan_params
params[:lesson_plan]
end
def lesson_plan_params
params.require(:lesson_plan).permit(:title, :synopsis)
end
end
Here are my factory definitions: (Maybe I don't need to define user_id in the lesson_plan factory?)
FactoryGirl.define do
factory :user do
sequence( :username ) { |n| "user#{n}" }
sequence( :email ) { |n| "foo#{n}#example.com" }
password 'foobarbaz'
password_confirmation 'foobarbaz'
created_at Time.now
updated_at Time.now
end
end
FactoryGirl.define do
factory :lesson_plan do
user_id 1
title "The French Revolution"
synopsis "Background and events leading up to the French Revolution"
end
end
And the test part is where I get stuck.
describe LessonPlansController do
let(:valid_attributes) { { } }
let(:valid_session) { {} }
# describe "GET index" do
it "assigns all lesson_plans as #lesson_plans" do
user=FactoryGirl.create(:user)
sign_in user
lesson_plan = LessonPlan.create! valid_attributes
get :index, {}, valid_session
assigns(:lesson_plans).should eq([lesson_plan])
end
end
I'm not sure what to put in valid_attributes and valid_session (or if I even need them). The test will get as far as signing in the user, but will fail on creation of the lesson_plan. Admittedly this is the default/generated test for rSpec, but I am not sure how to proceed.
Examples I have seen use a before block to set up the user. I haven't been able to find anything on the Devise wiki page covering how to write basic rSpec tests for a controller that requires the user to be logged in. Any pointers would be greatly appreciated!
"I'm not sure what to put in valid_attributes and valid_session (or if I even need them)."
Well that depends what you're testing for.. Say you're testing validations & want to ensure that a record not be created if x column is set to null... then you could try to specifically create a record with invalid attributes (e.g. column: nil) and expect the result to not return true; maybe you want to ensure that it IS created with valid attributes.
You can btw, use `attributes_for(:factory_name)`` since you're using FactoryGirl. And no you don't necessarily need to specify the user's id in your lesson plan factory; unless you always want it to reference user 1. You can simply reference user with no value. Check out http://everydayrails.com/2012/03/12/testing-series-intro.html and especially parts 3-5 for an introduction to testing with RSPec.. I found this a pretty easy to follow guide when I was getting started.
I'm trying to test associations in my Rspec controller tests. Problem is that Factory doesn't produce associations for the attributes_for command. So, following the suggestion in this post I defined my validate attributes in my controller spec like so:
def valid_attributes
user = FactoryGirl.create(:user)
country = FactoryGirl.create(:country)
valid_attributes = FactoryGirl.build(:entitlement, user_id: user.id, country_id: country.id, client: true).attributes.symbolize_keys
puts valid_attributes
end
However, when the controller test runs I still get the following errors:
EntitlementsController PUT update with valid params assigns the requested entitlement as #entitlement
Failure/Error: entitlement = Entitlement.create! valid_attributes
ActiveRecord::RecordInvalid:
Validation failed: User can't be blank, Country can't be blank, Client & expert are both FALSE. Please specify either a client or expert relationship, not both
Yet the the valid_attributes output in the terminal clearly shows that each valid_attribute has a user_id, country_id and expert is set to true:
{:id=>nil, :user_id=>2, :country_id=>1, :client=>true, :expert=>false, :created_at=>nil, :updated_at=>nil}
It looks like you have a puts as the last line in your valid_attributes method, which returns nil. That's why when you pass it to Entitlement.create! you get an error about user and country being blank, etc.
Try removing that puts line, so you have just:
def valid_attributes
user = FactoryGirl.create(:user)
country = FactoryGirl.create(:country)
FactoryGirl.build(:entitlement, user_id: user.id, country_id: country.id, client: true).attributes.symbolize_keys
end
Incidentally, you shouldn't really be creating users and countries and then passing their ids to build, you can do that in the factory itself just by including lines with user and country in the entitlement factory. When you run FactoryGirl.build(:entitlement) it will then automatically create them (but not actually save the entitlement record).
Suppose I have a model user, which has a uniqueness constraint on the email field
If I call Factory(:user) once all is well, but if I call it a second time it'll fail with an "entry already exists" error.
I'm currently using a simple helper to search for an existing entry in the DB before creating the factory...and calling any factory I make through that helper.
It works, but it's not entirely elegant, and considering how common I assume this problem must be, I'm guessing there's a better solution. So, is there an inbuilt way in factory girl to return_or_create a factory, instead of just charging ahead with create()? If not, how do most folk avoid duplicate entries with their factories?
Simple answer: use factory.sequence
If you have a field that needs to be unique you can add a sequence in factory_girl to ensure that it is never the same:
Factory.define :user do |user|
sequence(:email){|n| "user#{n}#factory.com" }
user.password{ "secret" }
end
This will increment n each time in order to produce a unique email address such as user52#factory.com. (See https://github.com/thoughtbot/factory_girl/wiki/Usage for more info)
However this isn't always great in Rails.env.development...
Over time I have found that this is not actually the most useful way to create unique email addresses. The reason is that while the factory is always unique for your test environment it's not always unique for your development environment and n resets itself as you start the environment up and down. In :test this isn't a problem because the database is wiped but in :development you tend to keep the same data for a while.
You then get collisions and find yourself having to manually override the email to something you know is unique which is annoying.
Often more useful: use a random number
Since I call u = Factory :user from the console on a regular basis I go instead with generating a random number. You're not guaranteed to avoid collisions but in practice it hardly ever happens:
Factory.define :user do |user|
user.email {"user_#{Random.rand(1000).to_s}#factory.com" }
user.password{ "secret" }
end
N.B. You have to use Random.rand rather than rand() because of a collision (bug?) in FactoryGirl [https://github.com/thoughtbot/factory_girl/issues/219](see here).
This frees you to create users at will from the command line regardless of whether there are already factory generated users in the database.
Optional extra for making email testing easier
When you get into email testing you often want to verify that an action by a particular user triggered an email to another user.
You log in as Robin Hood, send an email to Maid Marion and then go to your inbox to verify it. What you see in your inbox is something from user_842#factory.com. Who the hell is that?
You need to go back to your database to check whether the email was sent / received by whomever you expected it to be. Again this is a bit of a pain.
What I like to do instead is to generate the email using the name of the Factory user combined with a random number. This makes it far easier to check who things are coming from (and also makes collisions vanishingly unlikely). Using the Faker gem (http://faker.rubyforge.org/) to create the names we get:
Factory.define :user do |user|
user.first_name { Faker::Name::first_name }
user.last_name { Faker::Name::last_name }
user.email {|u| "#{u.first_name}_#{u.last_name}_#{Random.rand(1000).to_s}#factory.com" }
end
finally, since Faker sometimes generates names that aren't email-friendly (Mike O'Donnell) we need to whitelist acceptable characters: .gsub(/[^a-zA-Z1-10]/, '')
Factory.define :user do |user|
user.first_name { Faker::Name::first_name }
user.last_name { Faker::Name::last_name }
user.email {|u| "#{u.first_name.gsub(/[^a-zA-Z1-10]/, '')}_#{u.last_name.gsub(/[^a-zA-Z1-10]/, '')}_#{Random.rand(1000).to_s}#factory.com" }
end
This gives us personable but unique emails such as robin_hood_341#factory.com and maid_marion_10#factory.com
Here's what I do to force the 'n' in my factory girl sequence to be the same as that object's id, and thereby avoid collisions:
First, I define a method that finds what the next id should be in app/models/user.rb:
def self.next_id
self.last.nil? ? 1 : self.last.id + 1
end
Then I call User.next_id from spec/factories.rb to start the sequence:
factory :user do
association(:demo)
association(:location)
password "password"
sequence(:email, User.next_id) {|n| "darth_#{n}#sunni.ru" }
end
I found this a nice way to be sure the tests will always pass.
Otherwise you can not be sure the 100% of the times you will create a unique email.
FactoryGirl.define do
factory :user do
name { Faker::Company.name }
email { generate(:email) }
end
sequence(:email) do
gen = "user_#{rand(1000)}#factory.com"
while User.where(email: gen).exists?
gen = "user_#{rand(1000)}#factory.com"
end
gen
end
end
If you only need to generate a few values for attributes, you can also add a method to String, which keeps track of the prior strings used for an attribute. You could then do something like this:
factory :user do
fullname { Faker::Name.name.unique('user_fullname') }
end
I use this approach for seeding. I wanted to avoid sequence numbers, because they do not look realistic.
Here the String extension which makes this happen:
class String
# Makes sure that the current string instance is unique for the given id.
# If you call unique multiple times on equivalent strings, this method will suffix it with a upcounting number.
# Example:
# puts "abc".unique("some_attribute") #=> "abc"
# puts "abc".unique("some_attribute") #=> "abc-1"
# puts "abc".unique("some_attribute") #=> "abc-2"
# puts "abc".unique("other") #=> "abc"
#
# Internal:
# We keep a data structure of the following format:
# ##unique_values = {
# "some_for_id" => { "used_string_1" : 1, "used_string_2": 2 } # the numbers represent the counter to be used as suffix for the next item
# }
def unique(for_id)
##unique_values ||= {} # initialize structure in case this method was never called before
##unique_values[for_id] ||= {} # initialize structure in case we have not seen this id yet
counter = ##unique_values[for_id][self] || 0
result = (counter == 0) ? self : "#{self}-#{counter}"
counter += 1
##unique_values[for_id][self] = counter
return result
end
end
Caution: This should not be used for lots of attributes, since we track all prior strings (optimizations possible).
I current test uniqueness validations in rspec using something like the following:
#unique = Unique.create!(:unique_field => 'unique_value')
Unique.new(:unique_field => 'unique_value').should_not be_valid
Ideally, I'd like to get my nose out of the database for uniqueness validations. Any ideas how to go about doing that?
validates_uniqueness_of does not guarantee the absence of duplicate record insertions, because uniqueness checks on the application level are inherently prone to race conditionsmore. To get around this problem you might define unique index on on your unique field or write some workaround to have tests that don't depend on your database however I don't see any point in writing these tests. You'll just have some useless code in your app.
you can change the unique value dynamically by editing your factory to change every time you run test
for example
Factory.define :user do |f|
f.sequence(:username) { |n| "foo#{n}" }
f.password "foobar"
f.password_confirmation { |u| u.password }
f.sequence(:email) { |n| "foo#{n}#example.com" }
end
iHope it helpful , good luck
source : Railscasts Episode #158: Factory Girl
I'm new to Rails testing and I'm confuse with the nature of rails integration test data:
test "should register new user and login" do
..
# create user
post users_path, :user => {:username => "newuser", :email => "newuser#gmail.com",
:password => "secret",
:password_confirmation => "secret"}
assert assigns(:user).valid?
..
end
test "another should register new user and login" do
..
# create user
post users_path, :user => {:username => "newuser", :email => "newuser#gmail.com",
:password => "secret",
:password_confirmation => "secret"}
assert assigns(:user).valid?
..
end
In the User model i have a validation to ensure that :username and :email are unique. But how come the test was able to post both data with no complain/error/invalid? Any help is appreciated, I thought there's a single DB only for the test and the test data should've clashed. Thanks.
Good question. Before the test framework (Test::Unit) runs each test it resets the database to its original state. So before each test you are guaranteed that the DB only has the fixture data and nothing else.
This is helpful. You don't want the data from one test still to be there when you start the next test. If it were, the DB state would be inconsistent if you did something like run a test by itself (instead of the whole suite) or run tests in a different order.
The solution: you can write a test that specifically verifies that the :username and :email are unique. Before I give an example, I should mention that it looks like you wrote a functional test for this validation. This is tempting because you're verifying the behavior the user will see, but the place to test validation is in the unit tests because validation rules in Rails get pushed down to the models. (To keep validation DRY. You don't have to duplicate your validation rules across a bunch of controllers)
So here's one way you could write the unit test:
test "should validate unique email" do
attributes = { :username => "newuser", :email => "newuser#gmail.com", :password => "secret", :password_confirmation => "secret" }
# create user
user1 = User.create(attributes)
assert_nil user1.errors.on(:username)
assert_nil user1.errors.on(:email)
# create another user with the same name and email
user2 = User.create(attributes)
assert_not_nil user2.errors.on(:username)
assert_not_nil user2.errors.on(:email)
end
For a good article about this, see John Nunemaker's blog post from two years ago. Ignore the accidental HTML tags in his code samples. Nunemaker is an awesome Rails blogger.
There are some alternate testing libraries like Shoulda that have built-in functions to validate that your model validates uniqueness. But since you're new to Rails I'd recommend sticking with the default test framework for now. (In fact the folks at 37Signals who invented Rails still use the default too, so that says a lot for it. I also prefer to stick with the default.)
By the way, you can format code in a StackOverflow question by indenting it with four spaces.