thoughtbot / factory_bot

A library for setting up Ruby objects as test data.

Home Page:https://thoughtbot.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

should factories validate their callbacks?

alexdean opened this issue · comments

This is only a question at this point. Could become a feature request depending on feedback.

Problem this feature will solve

I recently created a factory like

FactoryBot.define do
  factory :location, class: Location do
    before(:build) do |location, evaluator|
      do_a_thing
    end
  end
end

I was very confused about why my do_a_thing method was never being called. I eventually (re)-disovered that before(:build) isn't a supported callback. Duh. I changed this to after(:build) and problem solved. Reflecting on why I made this mistake, I'd say I'm just very used to the ActiveRecord callback system, where most callback events support both a before and after permutation.

I wondered if there would've been a way for FactoryBot to warn me that I was defining a callback that would never be called, since that would've saved a lot of time & head-scratching.

To be clear: I don't think what FactoryBot is doing here is wrong. I was just looking for a way to avoid this pitfall in the future.

I poked in the code a while, and it seems like this kind of validation might be hard to implement in general because strategies don't express which callbacks they support.

  1. Is that a correct assessment? This is my first time digging into FactoryBot internals so I may have missed something.
  2. Could this kind of validation be added for the default :build and :create strategies maybe? I have to guess that would cover the large majority of FactoryBot users, so it might be worthwhile even if the general case (including custom strategies) isn't easy.

Desired solution

This is only a guess...

# lib/factory_bot/strategy/build.rb
module FactoryBot
  module Strategy
    class Build
      def association(runner)
        runner.run
      end

      def result(evaluation)
        evaluation.object.tap do |instance|
          evaluation.notify(:after_build, instance)
        end
      end

      def to_sym
        :build
      end

      # NEW THING: allow strategies to express which callbacks they support
      def supported_callbacks
        [:after_build]
      end
    end
  end
end

# lib/factory_bot/factory.rb
module FactoryBot
  class Factory
    def run(build_strategy, overrides, &block)
      block ||= ->(result) { result }
      compile

      strategy = StrategyCalculator.new(build_strategy).strategy.new

      # THIS IS THE NEW PART
      if strategy.respond_to?(:supported_callbacks)
        unknown_callbacks = @definition.callbacks.map(&:name) - strategy.supported_callbacks
        if unknown_callbacks.size > 0
          # maybe a warning here?
        end
      end
  
      # continue with process here... snipped from example for clarity.
    end

From the existing callback specs, it seems like maybe this isn't even a desired feature? I noticed a factory in the test suite which defines a number of callbacks which would never be invoked by a normal FactoryBot.build(), but the tests use this factory for many :build calls.

I only raise this because the current approach (to silently ignore callbacks which are not understood by the current strategy) definitely caused me some confusion and I thought I'd ask if others might have the same issue. Thanks for your feedback & consideration!

Good find on that spec. Yeah, I think the challenge is that a callback might be valid for one strategy but not another. Like after(:create) doesn't do anything when using FactoryBot.build, but it does do something for FactoryBot.create.

Like after(:create) doesn't do anything when using FactoryBot.build, but it does do something for FactoryBot.create.

oh good point. Hadn't considered that. So I guess this is a wontfix. :( Thanks for the input!