thoughtbot / factory_bot

A library for setting up Ruby objects as test data.
https://thoughtbot.com
MIT License
7.92k stars 2.61k forks source link

Add find_or_create_by Method #1681

Open hatsu38 opened 3 months ago

hatsu38 commented 3 months ago

Problem this feature will solve

When setting up test data with FactoryBot, it can be cumbersome and repetitive to check if a record exists before creating it. For instance, in our current test setup, we have to manually use find_by followed by create if the record is not found.

RSpec.describe User do 
  before(:all) { Fixtures.setup_basics }

  it do
    # aa
  end

  it do
    # aa
  end
end

module Fixtures
  def self.setup_basics
    setup_normal_plan
    setup_high_plan
  end

  def self.normal_plan
      a_token = FactoryBot.create(:token :free)
      b_token = FactoryBot.create(:token, :normal)
     # etc...
  end

  def self.high_plan
    a_token = Token.find_by(name: "free") || FactoryBot.create(:token :free)
    b_token = Token.find_by(name: "normal") || FactoryBot.create(:token :normal)
  end
end

Desired solution

  def self.high_plan
    a_token = Token.find_or_create_by(:token :free)
    b_token = Token.find_or_create_by(:token :normal)
  end

Alternatives considered

  def self.high_plan
    a_token = Token.find_by(name: "free") || FactoryBot.create(:token :free)
    b_token = Token.find_by(name: "normal") || FactoryBot.create(:token :normal)
  end

Additional context

colinross commented 3 weeks ago

I believe that is somewhat intended. Sharing data between test runs is usually a code smell, either in the runtime code or your testing code.

Assuming you have some level of cleanup strategy, why not just create a new token each time? is the name a hardcoded value in your runtime code? if the object is highly complex and/or for instance, provided by an external service, have you thought of using mocks?

I get what you are asking for here, but passive (ab)use of a Find_or_create (in a test suite for actor objects) often leads to flaky test suites.