From 1315b6129b99e317a5e951888461d4d98149ee35 Mon Sep 17 00:00:00 2001 From: Grant Hutchins Date: Fri, 4 Sep 2026 16:47:26 -0500 Subject: [PATCH] Use with_model for test database setup with_model 2.4 adds Test::Unit lifecycle integration, allowing the test suite to declare its tables instead of rebuilding the connection and schema procedurally at individual call sites. Replace TestDatabase with fixed-name with_table declarations so model inference and fixture filenames remain unchanged. Reset fixture and column caches at their lifecycle boundaries, and preserve generated fixture cleanup. --- fixture_builder.gemspec | 1 + test/fixture_builder_test.rb | 20 +--- ...cy_fixture_mode_fixture_generation_test.rb | 3 +- test/legacy_fixture_mode_test.rb | 3 +- test/namer_test.rb | 3 +- test/support/test_database.rb | 97 ------------------- test/support/test_schema.rb | 70 +++++++++++++ test/test_helper.rb | 8 +- 8 files changed, 81 insertions(+), 124 deletions(-) delete mode 100644 test/support/test_database.rb create mode 100644 test/support/test_schema.rb diff --git a/fixture_builder.gemspec b/fixture_builder.gemspec index 964a46d..af47a71 100644 --- a/fixture_builder.gemspec +++ b/fixture_builder.gemspec @@ -34,4 +34,5 @@ Gem::Specification.new do |s| s.add_development_dependency "rake" s.add_development_dependency "sqlite3" s.add_development_dependency "test-unit" + s.add_development_dependency "with_model" end diff --git a/test/fixture_builder_test.rb b/test/fixture_builder_test.rb index 957f02e..e46d57d 100644 --- a/test/fixture_builder_test.rb +++ b/test/fixture_builder_test.rb @@ -9,7 +9,7 @@ def self.table_name end class FixtureBuilderTest < Test::Unit::TestCase - include TestDatabase + include TestSchema def teardown FixtureBuilder.instance_variable_set(:@configuration, nil) @@ -27,7 +27,6 @@ def test_name_with end def test_ivar_naming - create_and_blow_away_old_db force_fixture_generation FixtureBuilder.configure do |fbuilder| @@ -41,7 +40,6 @@ def test_ivar_naming end def test_serialization - create_and_blow_away_old_db force_fixture_generation FixtureBuilder.configure do |fbuilder| @@ -56,7 +54,6 @@ def test_serialization end def test_dates_are_iso_formatted_without_mutating_global_date_formats - create_and_blow_away_old_db force_fixture_generation default_date_format_exists = Date::DATE_FORMATS.key?(:default) @@ -97,7 +94,6 @@ def test_dates_are_iso_formatted_without_mutating_global_date_formats end def test_do_not_include_virtual_attributes - create_and_blow_away_old_db force_fixture_generation FixtureBuilder.configure do |fbuilder| @@ -111,7 +107,6 @@ def test_do_not_include_virtual_attributes end def test_generated_columns_are_excluded_for_model_backed_tables - create_and_blow_away_old_db force_fixture_generation table_name = GeneratedCreature.table_name @@ -131,7 +126,6 @@ def test_generated_columns_are_excluded_for_model_backed_tables end def test_generated_columns_are_excluded_for_raw_query_tables - create_and_blow_away_old_db force_fixture_generation table_name = GENERATED_COLUMN_RECORDS_TABLE @@ -156,7 +150,6 @@ def test_generated_columns_are_excluded_for_raw_query_tables end def test_raw_query_select_aliases_are_preserved - create_and_blow_away_old_db force_fixture_generation table_name = GENERATED_COLUMN_RECORDS_TABLE @@ -185,7 +178,6 @@ def test_raw_query_select_aliases_are_preserved end def test_generated_columns_come_from_the_model_table_name - create_and_blow_away_old_db force_fixture_generation table_name = RELOCATED_CREATURES_TABLE @@ -206,7 +198,6 @@ def test_generated_columns_come_from_the_model_table_name end def test_custom_json_attribute_type_round_trips_through_fixtures - create_and_blow_away_old_db force_fixture_generation wizard_data = WizardData.new( level: 99, @@ -380,7 +371,6 @@ def test_fixtures_dir end def test_rebuilding_due_to_differing_file_hashes - create_and_blow_away_old_db force_fixture_generation_due_to_differing_file_hashes FixtureBuilder.configure do |fbuilder| @@ -395,7 +385,6 @@ def test_rebuilding_due_to_differing_file_hashes end def test_rebuilds_when_generated_fixture_hashes_differ - create_and_blow_away_old_db force_fixture_generation FixtureBuilder.configure do |fbuilder| @@ -444,7 +433,6 @@ def test_rebuilds_parsed_invalid_manifest(payload) end def test_malformed_manifest_raises_without_running_factory - create_and_blow_away_old_db manifest_path = File.expand_path("../tmp/fixture_builder.yml", __dir__) File.write(manifest_path, "---\ninvalid: [\n") factory_called = false @@ -471,7 +459,6 @@ def test_lock_path_tracks_fixture_builder_file end def test_fresh_manifest_returns_without_acquiring_lock - create_and_blow_away_old_db force_fixture_generation builds = 0 lock_path = nil @@ -499,7 +486,6 @@ def test_fresh_manifest_returns_without_acquiring_lock end def test_skips_rebuild_for_valid_empty_fixture_snapshot - create_and_blow_away_old_db force_fixture_generation fixture_snapshot = Dir[test_path("fixtures/*.yml")].to_h do |filename| [filename, File.binread(filename)] @@ -531,7 +517,6 @@ def test_skips_rebuild_for_valid_empty_fixture_snapshot end def test_raising_after_build_invalidates_manifest_and_retries - create_and_blow_away_old_db force_fixture_generation builds = 0 factory = proc do @@ -549,7 +534,6 @@ def test_raising_after_build_invalidates_manifest_and_retries generated_fixture = YAML.safe_load_file(fixture_path) generated_fixture["enty"]["retired_column"] = "bogus" File.write(fixture_path, generated_fixture.to_yaml) - create_and_blow_away_old_db FixtureBuilder.instance_variable_set(:@configuration, nil) assert_raise(RuntimeError) do @@ -573,7 +557,6 @@ def test_raising_after_build_invalidates_manifest_and_retries end def test_sha256_manifest_digests_when_deprecated_use_sha1_digests_is_enabled - create_and_blow_away_old_db force_fixture_generation_due_to_differing_file_hashes source_path = Pathname.new(test_path("fixture_builder_test.rb")) @@ -613,7 +596,6 @@ def test_sha256_manifest_digests_when_deprecated_use_sha1_digests_is_enabled private def assert_manifest_rebuilds(payload) - create_and_blow_away_old_db force_fixture_generation builds = 0 factory = proc do diff --git a/test/legacy_fixture_mode_fixture_generation_test.rb b/test/legacy_fixture_mode_fixture_generation_test.rb index 029193e..8048403 100644 --- a/test/legacy_fixture_mode_fixture_generation_test.rb +++ b/test/legacy_fixture_mode_fixture_generation_test.rb @@ -3,10 +3,9 @@ require File.expand_path(File.join(File.dirname(__FILE__), "test_helper")) class LegacyFixtureModeFixtureGenerationTest < Test::Unit::TestCase - include TestDatabase + include TestSchema def setup - create_and_blow_away_old_db force_fixture_generation FixtureBuilder.configure do |fbuilder| diff --git a/test/legacy_fixture_mode_test.rb b/test/legacy_fixture_mode_test.rb index 543e5a3..e6ff38a 100644 --- a/test/legacy_fixture_mode_test.rb +++ b/test/legacy_fixture_mode_test.rb @@ -3,10 +3,9 @@ require File.expand_path(File.join(File.dirname(__FILE__), "test_helper")) class LegacyFixtureModeTest < Test::Unit::TestCase - include TestDatabase + include TestSchema def setup - create_and_blow_away_old_db force_fixture_generation end diff --git a/test/namer_test.rb b/test/namer_test.rb index 1b9fd47..29bec12 100644 --- a/test/namer_test.rb +++ b/test/namer_test.rb @@ -23,7 +23,7 @@ def self.model_class = NamerTestModel end class NamerTest < Test::Unit::TestCase - include TestDatabase + include TestSchema def setup configuration = FixtureBuilder::Configuration.new @@ -64,7 +64,6 @@ def test_name_not_unique_across_tables end def test_populate_custom_names_from_current_fixture_sets - create_and_blow_away_old_db fixture_sets = ActiveRecord::FixtureSet.create_fixtures( test_path("legacy_fixtures"), MagicalCreature.table_name diff --git a/test/support/test_database.rb b/test/support/test_database.rb deleted file mode 100644 index 70f247e..0000000 --- a/test/support/test_database.rb +++ /dev/null @@ -1,97 +0,0 @@ -# frozen_string_literal: true - -# Shared test database used by the FixtureBuilder tests. -# -# Test classes include this module and call +create_and_blow_away_old_db+ from -# their own +setup+ or from individual tests, exactly as they did when this -# lived as a top-level helper method. -# -# Alongside the `magical_creatures` table the tests share, the schema carries -# the tables that exist only to exercise FixtureBuilder's database-generated -# column handling. -# -# Ownership of the schema and of the fixture files it causes FixtureBuilder to -# write live together: including this module registers a teardown that removes -# those fixture files, so every test class that builds the shared schema cleans -# up after itself rather than depending on another class running later. -module TestDatabase - CONFIGURATION = {"adapter" => "sqlite3", "database" => ":memory:"}.freeze - - GENERATED_CREATURES_TABLE = "generated_creatures" - GENERATED_COLUMN_RECORDS_TABLE = "generated_column_records" - RELOCATED_CREATURES_TABLE = "relocated_creatures" - CREATURE_ARCHIVE_TABLE = "creature_archive" - - # The only tables created solely to exercise generated columns, and therefore - # the only fixture files a run is allowed to delete. `magical_creatures` is - # user-authored fixture data and is deliberately absent. - GENERATED_TEST_TABLES = [ - GENERATED_CREATURES_TABLE, - GENERATED_COLUMN_RECORDS_TABLE, - RELOCATED_CREATURES_TABLE, - CREATURE_ARCHIVE_TABLE - ].freeze - - def self.included(base) - base.teardown :clean_up_generated_fixture_files - end - - def create_and_blow_away_old_db - ActiveRecord::Base.configurations = {"test" => CONFIGURATION} - ActiveRecord::Base.establish_connection(:test) - connection = ActiveRecord::Base.connection - connection.create_table(:magical_creatures, force: true) do |t| - t.column :name, :string - t.column :species, :string - t.column :powers, :string - t.column :wizard_data, :json - t.column :born_on, :date - t.column :deleted, :boolean, default: false, null: false - end - - # Inferable as the `GeneratedCreature` model, so the model-backed extraction - # path sees a database-generated column. - create_generated_column_table(GENERATED_CREATURES_TABLE) - - # No inferable model, so the raw-query extraction path sees a - # database-generated column. - create_generated_column_table(GENERATED_COLUMN_RECORDS_TABLE) - - # The table FixtureBuilder iterates (`relocated_creatures`) alongside the - # differently named table `RelocatedCreature` actually reads. - # - # The two tables expose deliberately incompatible schemas: the iterated - # table's only writable column is `unrelated` and its `name` is - # database-generated, while the model's table has a writable `name`. Reading - # generated columns from the iterated table instead of the model's table - # therefore strips `name` from the fixture. - connection.create_table(RELOCATED_CREATURES_TABLE, force: true) do |t| - t.string :unrelated - t.virtual :name, type: :string, as: "upper(unrelated)", stored: true - end - connection.create_table(CREATURE_ARCHIVE_TABLE, force: true) do |t| - t.string :name, null: false - end - - GeneratedCreature.reset_column_information - RelocatedCreature.reset_column_information - end - - # Creates a table with a writable `name` column and a stored - # database-generated `name_length` column. - def create_generated_column_table(table_name) - ActiveRecord::Base.connection.create_table(table_name, force: true) do |t| - t.string :name, null: false - t.virtual :name_length, type: :integer, as: "length(name)", stored: true - end - end - - # FixtureBuilder writes a fixture file for every table it iterates, so runs - # leave behind output for the generated-column tables above. Delete exactly - # those files, never the fixture directory or fixtures the repository owns. - def clean_up_generated_fixture_files - GENERATED_TEST_TABLES.each do |table_name| - FileUtils.rm_f(test_path("fixtures/#{table_name}.yml")) - end - end -end diff --git a/test/support/test_schema.rb b/test/support/test_schema.rb new file mode 100644 index 0000000..aafc036 --- /dev/null +++ b/test/support/test_schema.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +# Shared fixed-name schemas used by the FixtureBuilder tests. +# +# FixtureBuilder derives model and fixture names from the table name, so these +# tables deliberately use their production-like fixed names rather than +# with_model's generated names. +module TestSchema + GENERATED_CREATURES_TABLE = "generated_creatures" + GENERATED_COLUMN_RECORDS_TABLE = "generated_column_records" + RELOCATED_CREATURES_TABLE = "relocated_creatures" + CREATURE_ARCHIVE_TABLE = "creature_archive" + + # The only tables created solely to exercise generated columns, and therefore + # the only fixture files a run is allowed to delete. `magical_creatures` is + # user-authored fixture data and is deliberately absent. + GENERATED_TEST_TABLES = [ + GENERATED_CREATURES_TABLE, + GENERATED_COLUMN_RECORDS_TABLE, + RELOCATED_CREATURES_TABLE, + CREATURE_ARCHIVE_TABLE + ].freeze + + def self.included(test_case) + # Tables are recreated for every test, so legacy fixtures need Rails' cache + # cleared before each setup can load them again. + test_case.setup(before: :prepend) { ActiveRecord::FixtureSet.reset_cache } + + test_case.with_table(:magical_creatures) do |t| + t.string :name + t.string :species + t.string :powers + t.json :wizard_data + t.date :born_on + t.boolean :deleted, default: false, null: false + end + + test_case.with_table(GENERATED_CREATURES_TABLE) do |t| + t.string :name, null: false + t.virtual :name_length, type: :integer, as: "length(name)", stored: true + end + + test_case.with_table(GENERATED_COLUMN_RECORDS_TABLE) do |t| + t.string :name, null: false + t.virtual :name_length, type: :integer, as: "length(name)", stored: true + end + + test_case.with_table(RELOCATED_CREATURES_TABLE) do |t| + t.string :unrelated + t.virtual :name, type: :string, as: "upper(unrelated)", stored: true + end + + test_case.with_table(CREATURE_ARCHIVE_TABLE) do |t| + t.string :name, null: false + end + + test_case.setup(after: :append) do + GeneratedCreature.reset_column_information + RelocatedCreature.reset_column_information + end + + # FixtureBuilder writes a fixture file for every generated-column table it + # iterates. Delete exactly those files, never repository-owned fixtures. + test_case.teardown(after: :append) do + GENERATED_TEST_TABLES.each do |table_name| + FileUtils.rm_f(test_path("fixtures/#{table_name}.yml")) + end + end + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb index c79eabe..ef569fc 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -21,6 +21,11 @@ def test_path(glob) require "active_support/concern" require "active_record" require "active_record/fixtures" +require "sqlite3" + +ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:") + +require "with_model/test_unit" def create_fixtures(*table_names, &block) fixture_set = ActiveRecord::FixtureSet @@ -41,9 +46,8 @@ def create_fixtures(*table_names, &block) end end -require "sqlite3" require "fixture_builder" -require_relative "support/test_database" +require_relative "support/test_schema" class WizardData attr_reader :level, :title, :allies