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?

+3


source to share


1 answer


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

      

+1


source







All Articles