Can I create invalid values ββwith FactoryGirl?
I have an email factory (spec / factories / email.rb):
FactoryGirl.define do
factory :email, class: String do
skip_create
transient do
username 'user'
subdomain 'mail'
domain_name 'example.com'
host { [subdomain, domain_name].compact.join('.') }
end
trait(:with_blank_host) { host '' }
trait(:with_blank_username) { username '' }
initialize_with { new("#{username}@#{host}") }
end
end
And I have spec (spec / models / user_spec.rb):
RSpec.describe User, type: :model do
# ...
it { is_expected.to_not allow_value(FactoryGirl.create(:email, :with_blank_host)).for(:email) }
it { is_expected.to_not allow_value(FactoryGirl.create(:email, :with_blank_username)).for(:email) }
end
Is it correct to use FactoryGirl this way? Is this bad practice?
source to share
Assuming no host / username email creation logic will be used anywhere other than tests. Why do you want to store it in a factory?
Why not just do:
FactoryGirl.define do
factory :email, class: String do
skip_create
transient do
username 'user'
subdomain 'mail'
domain_name 'example.com'
host { [subdomain, domain_name].compact.join('.') }
end
initialize_with { new("#{username}@#{host}") }
end
end
RSpec.describe User, type: :model do
# ...
it 'should not allow blank host' do
is_expected.to_not allow_value(FactoryGirl.create(:email, host: '')).for(:email)
end
it 'should not allow blank username' do
is_expected.to_not allow_value(FactoryGirl.create(:email, username: '').for(:email)
end
end
Also, does it really make sense to create a factory to create the email string? why not just use a string (it's easier):
is_expected.to_not allow_value(FactoryGirl.create(:email, host: '')).for(:email)
against
is_expected.to_not allow_value('test@').for(:email)
If you want to have a consistent email address across your test, why not put it in the user.
FactoryGirl.define do
factory :user
transient do
username 'user'
subdomain 'mail'
domain_name 'example.com'
host { [subdomain, domain_name].compact.join('.') }
end
before(:create) do |user, evaluator|
user.email = "#{username}@#{host}"
end
end
end
But if the logic belongs to those particular tests, why abstract it all into a factory instead of just using it before / callback. Typically, I only put the logic to be used in the spec files in the factory, everything else is in the correct before / after callback
RSpec.describe User, type: :model do
before :each do
# logic before each test of the file
end
context 'email validation'
before :each do
# logic before each test concerning the email validation
# useful to factor stuff that will be used multiple time
# e.g.
# @email_ok = 'user@example.com'
end
it 'should not allow blank host' do
# test specific setup
# useful for stuff only used in this test
# e.g
# email_no_host = 'user@'
# expectation
end
end
end
In short:
What you are doing will work, of course. This is not bad practice in and of itself. It just doesn't make sense IMHO.
EDITED
You can also add a helper inside the validation area to avoid making the model too fat:
RSpec.describe User, type: :model do
context 'email validation'
def generate_email(**opts)
options = {username: 'user', subdomain: 'mail', domain_name 'example.com'}.merge(opts)
username = options[:username]
host = options[:host] || "#{options[:subdomain]}.#{options[:domain_name]}"
"#{username}@#{host}"
end
it 'should not allow blank host' do
is_expected.to_not allow_value(generate_email host: '').for(:email)
end
# Here goes 100 other use of generate_email
end
end
source to share