thoughtbot / factory_bot

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

Dynamically define unique methods for each factory #1553

Open georgebrock opened 1 year ago

georgebrock commented 1 year ago

Problem this feature will solve

At GitHub we've recently adopted Sorbet for type annotations in our Ruby code, and we make extensive use of factory_bot in our test suite.

Both of these tools are great, but when using them together there's a problem: Sorbet can't statically determine the return type of factory_bot methods (#build, #create, etc.) because the return type depends on the arguments.

Desired solution

Dynamically defining a unique method for each strategy/factory combination would make it possible to provide type annotations, because the return type would now depend only on the method.

For example, instead of this:

thing = create(:thing, title: "Example")

We would be able to write this:

thing = create_thing(title: "Example")

I hacked together a little proof-of-concept, but it involves breaking encapsulation to get at the list of strategies:

module TypedFactoryMethods
  def self.included(host)
    # Egregious hack to get the list of strategies:
    strategies = FactoryBot::Internal.strategies.instance_eval { @items.keys }

    FactoryBot.factories.to_a.product(strategies).each do |factory, strategy|
      define_factory_method(host, strategy, factory.name)
    end
  end

  def self.define_factory_method(host, strategy, factory_name)
    host.define_method("#{strategy}_#{factory_name}") do |*traits_and_overrides, &block|
      FactoryBot::FactoryRunner.new(factory_name, strategy, traits_and_overrides).run(&block)
    end
  end
end

class Minitest::Test
  include TypedFactoryMethods
end

This alone wouldn't be the only thing requires for Sorbet typed factories: we would also need a Tapioca DSL compiler to produce RBI files with the type information.

Alternatives considered

Additional context

I'd be happy to open a PR here, but I figured it was worth opening an issue first to see if y'all were interested in this being a part of factory_bot first.

nathanmsmith commented 4 months ago

@georgebrock I'm also interested in using Sorbet and factory_bot together! Did you ever come to a solution here?