Skip to content

Include ActiveRecord::TestFixtures in the fixtures RBI - #2707

Open
mokevnin wants to merge 2 commits into
Shopify:mainfrom
mokevnin:fixtures-include
Open

Include ActiveRecord::TestFixtures in the fixtures RBI#2707
mokevnin wants to merge 2 commits into
Shopify:mainfrom
mokevnin:fixtures-include

Conversation

@mokevnin

@mokevnin mokevnin commented Aug 28, 2026

Copy link
Copy Markdown

Motivation

Rails mixes ActiveRecord::TestFixtures into ActiveSupport::TestCase from the :active_support_test_case load hook in rails/test_help.rb. That file is required from an app's test_helper.rb, so the hook never runs during RBI generation, the include never makes it into the gem RBI, and Sorbet doesn't see the class methods the module contributes through mixes_in_class_methods — most visibly fixtures:

class PostTest < ActiveSupport::TestCase
  fixtures :all # Method `fixtures` does not exist on `T.class_of(PostTest)` (7003)
end

Today every Rails app using fixtures shims this by hand (a sorbet/rbi/shims entry, or a local DSL compiler that emits nothing but the include). ActiveRecordFixtures already targets ActiveSupport::TestCase and already knows Rails is loaded, so it's the natural place to make the include explicit.

Implementation

One line in decoratemod.create_include("ActiveRecord::TestFixtures") — inside the existing create_path.

The early return now sits above the select! that drops fixture sets whose model constant can't be resolved, so the two cases it used to conflate are separated: an app with no fixture files at all still generates no RBI, while an app that has fixture files but no matching models gets an RBI with just the include (it writes fixtures :all too).

Tests

spec/tapioca/dsl/compilers/active_record_fixtures_spec.rb updated for the extra line, plus a new case for fixtures with no associated model at all; the whole file passes. bin/typecheck and rubocop are clean, and manual/ is regenerated via bin/docs.

@mokevnin
mokevnin requested a review from a team as a code owner August 28, 2026 17:07
Rails mixes `ActiveRecord::TestFixtures` into `ActiveSupport::TestCase` from the
`active_record.test_fixtures` railtie initializer. Initializers do not run during
gem RBI generation, so the include is missing from the gem RBI and Sorbet never
sees the class methods the module contributes through `mixes_in_class_methods` —
most visibly `fixtures`, so `fixtures :all` in a test case is an error under
`srb tc` unless every app shims it by hand.

The compiler already targets `ActiveSupport::TestCase` and knows Rails is loaded,
so it is the natural place to make the include explicit.

@KaanOzkan KaanOzkan left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this is a unique change but I think it's beneficial to have it in the compiler as you said.

# ~~~
#
# The `include` is generated because Rails mixes `ActiveRecord::TestFixtures` into
# `ActiveSupport::TestCase` from the `active_record.test_fixtures` railtie initializer, which

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't find that initializer, I think it's being included through this load hook https://github.com/rails/rails/blob/4130768a1b0d95da640ac792920e54988cf2d12f/railties/lib/rails/test_help.rb#L15-L24. Can we change the wording here?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, there is no such initializer — thanks for digging. It's the :active_support_test_case load hook in rails/test_help.rb, which an app requires from test_helper.rb, so it never runs during RBI generation. Reworded here, in manual/ and in the PR description.

return if method_names.empty?

root.create_path(constant) do |mod|
mod.create_include("ActiveRecord::TestFixtures")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it okay for this include to be skipped by the early return above?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it isn't — good catch. The select! above the early return drops fixture sets whose model constant can't be resolved, so an app that has fixture files but no matching models writes fixtures :all and still got no include.

Moved the early return above the select!, which separates the two cases it was conflating: no fixture files at all keeps generating nothing, everything else gets the include. Added a spec for fixtures with no associated model, which now yields an RBI with just the include.

…ed fixtures

Two fixes from review.

The include does not come from a railtie initializer — there is none. It comes
from the `:active_support_test_case` load hook in `rails/test_help.rb`, which an
app requires from `test_helper.rb`, so it never runs during RBI generation.
Reword the comment and the manual accordingly.

The early return sat below the `select!` that drops fixture sets whose model
constant cannot be resolved, so an app with fixture files but no matching models
wrote `fixtures :all` and still got no include. Move the return above the
`select!`: apps with no fixture files at all keep generating nothing, everything
else gets the include.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants