fixture_kit 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 311fd09f37153fae9d8a09e3e41357c38f18e1e343794a758ae943dfdca3c40a
4
- data.tar.gz: ee4257fc3c840f809a25204f300c766f872688c5c7417dc22155f001ba424882
3
+ metadata.gz: b9c12d67129c3c71b587913a62cea49c41f55a7ab9c0f4308961906517f7b6f7
4
+ data.tar.gz: 92e5dec6ccdb3084d5b699fd1f462fb145667dd46caeb7f65aba8d782244bdbd
5
5
  SHA512:
6
- metadata.gz: bd8c58de66c08eeceab67e4f0b83a8a95d25291401272e96b51725db2b7a9f2b2592c24f1d6f6cb7b4f6b4716fa93fc768c4fc81afcf1974b9339e14b1b61fa9
7
- data.tar.gz: 8d335927771e40d35142a69ee9bf911fcfb6c660af6cde28269a16643e86a468c017cf2f54e362f09d90a78bfeffe6320071d0230f6240969fc495de0e91c8f2
6
+ metadata.gz: 75484ce7d299f78263f0c268a39eacb4c33896075b90138ed6741e253b95d312144ced9481ae2e7e6328a4b8817bf8e566c7d56df86975e236b2f2a248643376
7
+ data.tar.gz: c7644edbec83c88df2d5cc24a206ccabd4d3f1077a199fd2317c81e7e128801a58ed86738b229ed679b6ca3c1b979fe0f2b5adbf2334ed4ab16dbd2fa04b3ccf
data/README.md CHANGED
@@ -8,7 +8,7 @@ Test data setup is slow. Every `Model.create!` or `FactoryBot.create` hits the d
8
8
 
9
9
  ## The Solution
10
10
 
11
- FixtureKit caches database records as raw SQL INSERT statements. On first use, it executes your fixture definition, captures the resulting database state, and generates optimized batch INSERT statements. Subsequent loads replay these statements directlyno ORM overhead, no callbacks, just fast SQL.
11
+ FixtureKit caches database records as raw SQL INSERT statements. It executes your fixture definition once, captures the resulting database state, and generates optimized batch INSERT statements. Fixture loads then replay these statements directly: no ORM overhead, no callbacks, just fast SQL.
12
12
 
13
13
  Combined with RSpec's transactional fixtures, each test runs in a transaction that rolls back—so cached data can be reused across tests without cleanup.
14
14
 
@@ -26,7 +26,7 @@ end
26
26
 
27
27
  ### 1. Define a Fixture
28
28
 
29
- Create fixture files in `spec/fixture_kit/`. Use whatever method you prefer to create records—FixtureKit doesn't care.
29
+ Create fixture files in `spec/fixture_kit/` (or `test/fixture_kit/` for test-unit/minitest-style setups). Use whatever method you prefer to create records.
30
30
 
31
31
  **Using ActiveRecord directly:**
32
32
 
@@ -117,60 +117,80 @@ RSpec.configure do |config|
117
117
  end
118
118
  ```
119
119
 
120
+ When you call `fixture "name"` in an example group, FixtureKit registers that fixture with its runner.
121
+
122
+ ### 4. Configure Minitest
123
+
124
+ ```ruby
125
+ # test/test_helper.rb
126
+ require "fixture_kit/minitest"
127
+
128
+ class ActiveSupport::TestCase
129
+ self.use_transactional_tests = true
130
+ end
131
+ ```
132
+
133
+ When you call `fixture "name"` in a test class, FixtureKit registers that fixture with its runner and mounts it during test setup.
134
+
120
135
  ## Configuration
121
136
 
122
137
  ```ruby
123
138
  # spec/support/fixture_kit.rb
124
139
  FixtureKit.configure do |config|
125
- # Where fixture definitions live (default: spec/fixture_kit)
140
+ # Where fixture definitions live (default: fixture_kit).
141
+ # Framework entrypoints set a framework-specific default:
142
+ # - fixture_kit/rspec -> spec/fixture_kit
143
+ # - fixture_kit/minitest -> test/fixture_kit
126
144
  config.fixture_path = Rails.root.join("spec/fixture_kit").to_s
127
145
 
128
146
  # Where cache files are stored (default: tmp/cache/fixture_kit)
129
147
  config.cache_path = Rails.root.join("tmp/cache/fixture_kit").to_s
130
148
 
131
- # Whether to regenerate caches on every run (default: true)
132
- config.autogenerate = true
149
+ # Wrapper used to isolate generation work (default: FixtureKit::MinitestIsolator)
150
+ # config.isolator = FixtureKit::MinitestIsolator
151
+ # config.isolator = FixtureKit::RSpecIsolator
133
152
 
134
- # Optional: customize how pregeneration is wrapped.
135
- # Default is FixtureKit::TestCase::Generator.
136
- # config.generator = FixtureKit::TestCase::Generator
153
+ # Optional callback, called right before a fixture cache is generated.
154
+ # Called on first generation and forced regeneration.
155
+ # Receives the fixture name as a String.
156
+ # config.on_cache = ->(fixture_name) { puts "cached #{fixture_name}" }
137
157
  end
138
158
  ```
139
159
 
140
- Custom generators should subclass `FixtureKit::Generator` and implement `#run`.
141
- `#run` receives the pregeneration block and should execute it in whatever lifecycle you need.
160
+ Custom isolators should subclass `FixtureKit::Isolator` and implement `#run`.
161
+ `#run` receives the generation block and should execute it in whatever lifecycle you need.
162
+
163
+ By default, FixtureKit uses `FixtureKit::MinitestIsolator`, which runs generation inside an internal `ActiveSupport::TestCase` and removes that harness case from minitest runnables.
164
+
165
+ When using `fixture_kit/rspec`, FixtureKit sets `FixtureKit::RSpecIsolator`. It runs generation inside an internal RSpec example, and uses a null reporter so harness runs do not count toward suite example totals.
142
166
 
143
- ### Autogenerate
167
+ ## Lifecycle
144
168
 
145
- When `autogenerate` is `true` (the default), FixtureKit clears all caches at the start of each test run, then regenerates them on first use. Subsequent tests that use the same fixture reuse the cache from earlier in the run. This ensures your test data always matches your fixture definitions.
169
+ Fixture generation is managed by `FixtureKit::Runner`.
146
170
 
147
- When `autogenerate` is `false`, FixtureKit pre-generates all fixture caches at suite start. This runs through the configured `generator`, and still rolls back database changes.
171
+ 1. Calling `fixture "name"` registers the fixture with the runner.
172
+ 2. Runner `start`:
173
+ - clears `cache_path` (unless preserve-cache is enabled),
174
+ - generates caches for all already-registered fixtures.
175
+ 3. If new tests are loaded after start (for example, queue-mode CI runners), newly registered fixtures are cached immediately.
176
+ 4. At test runtime, `fixture` mounts from cache and returns a `Repository`.
148
177
 
149
- By default, FixtureKit uses `FixtureKit::TestCase::Generator`, which runs pregeneration inside an internal `ActiveSupport::TestCase` so setup/teardown hooks and transactional fixture behavior run as expected. The internal test case is removed from Minitest runnables, so it does not count toward suite totals.
178
+ When runner start happens:
150
179
 
151
- When using `fixture_kit/rspec`, FixtureKit sets `FixtureKit::RSpec::Generator` as the generator. This runs pregeneration inside an internal RSpec example so your normal `before`/`around`/`after` hooks apply. The internal example uses a null reporter, so it does not count toward suite example totals.
180
+ - `fixture_kit/rspec`: in `before(:suite)`.
181
+ - `fixture_kit/minitest`: lazily during test setup for the first test class that declares `fixture`.
152
182
 
153
183
  ### Preserving Cache Locally
154
184
 
155
- If you want to skip cache clearing at suite start (e.g., to reuse caches across test runs during local development), set the `FIXTURE_KIT_PRESERVE_CACHE` environment variable:
185
+ If you want to skip cache clearing when the runner starts (e.g., to reuse caches across test runs during local development), set the `FIXTURE_KIT_PRESERVE_CACHE` environment variable:
156
186
 
157
187
  ```bash
158
188
  FIXTURE_KIT_PRESERVE_CACHE=1 bundle exec rspec
159
189
  ```
160
190
 
161
- This is useful when you're iterating on tests and your fixture definitions haven't changed.
162
-
163
- ### CI Setup
164
-
165
- For CI, set `autogenerate` to `false`. FixtureKit will automatically generate any missing caches at suite start:
191
+ Truthy values are case-insensitive: `1`, `true`, `yes`.
166
192
 
167
- ```ruby
168
- FixtureKit.configure do |config|
169
- config.autogenerate = !ENV["CI"]
170
- end
171
- ```
172
-
173
- This means CI "just works" - no need to pre-generate caches or commit them to the repository. The first test run will generate all caches, and subsequent runs (if caches are preserved between builds) will reuse them.
193
+ This is useful when you're iterating on tests and your fixture definitions haven't changed.
174
194
 
175
195
  ## Nested Fixtures
176
196
 
@@ -189,13 +209,13 @@ fixture "teams/sales"
189
209
 
190
210
  ## How It Works
191
211
 
192
- 1. **First load (cache miss)**: FixtureKit executes your definition block, subscribes to `sql.active_record` notifications to track which tables received INSERTs, queries all records from those tables, and generates batch INSERT statements with conflict handling (`INSERT OR IGNORE` for SQLite, `ON CONFLICT DO NOTHING` for PostgreSQL, `INSERT IGNORE` for MySQL).
212
+ 1. **Cache generation**: FixtureKit executes your definition block inside the configured isolator, subscribes to `sql.active_record` notifications to track created/updated/deleted models, queries those model tables, and caches INSERT statements for current table contents.
193
213
 
194
- 2. **Subsequent loads (cache hit)**: FixtureKit loads the cached JSON file and executes the raw SQL INSERT statements directly. No ORM instantiation, no callbacks—just fast SQL execution.
214
+ 2. **Mounting**: FixtureKit loads the cached JSON file, clears each tracked table, and executes the raw SQL INSERT statements directly. No ORM instantiation, no callbacks.
195
215
 
196
- 3. **In-memory caching**: Once a cache file is parsed, the data is stored in memory. Multiple tests using the same fixture within a single test run don't re-read or re-parse the JSON file.
216
+ 3. **Repository build**: FixtureKit resolves exposed records by model + id and returns a `Repository` for method-based access.
197
217
 
198
- 4. **Transaction isolation**: RSpec's `use_transactional_fixtures` wraps each test in a transaction that rolls back, so data doesn't persist between tests.
218
+ 4. **Transaction isolation**: Use framework transactions (`use_transactional_fixtures` in RSpec, `use_transactional_tests` in Minitest) so test writes roll back and cached data can be reused safely between tests.
199
219
 
200
220
  ### Cache Format
201
221
 
@@ -216,7 +236,7 @@ Caches are stored as JSON files in `tmp/cache/fixture_kit/`:
216
236
  ```
217
237
 
218
238
  - **records**: Maps model names to their INSERT statements. Using model names (not table names) allows FixtureKit to use the correct database connection for multi-database setups.
219
- - **exposed**: Maps fixture accessor names to their model class and ID for querying after cache replay
239
+ - **exposed**: Maps fixture accessor names to their model class and ID for querying after cache replay.
220
240
 
221
241
  ## Cache Management
222
242
 
@@ -225,7 +245,7 @@ Delete the cache directory to force regeneration:
225
245
  rm -rf tmp/cache/fixture_kit
226
246
  ```
227
247
 
228
- Caches are automatically cleared at suite start when `autogenerate` is enabled, so manual clearing is rarely needed.
248
+ Caches are cleared at runner start unless `FIXTURE_KIT_PRESERVE_CACHE` is truthy.
229
249
 
230
250
  ## Multi-Database Support
231
251
 
@@ -7,109 +7,61 @@ require "active_support/inflector"
7
7
 
8
8
  module FixtureKit
9
9
  class Cache
10
- # In-memory cache to avoid re-reading/parsing JSON for every test
11
- @memory_cache = {}
10
+ include ConfigurationHelper
12
11
 
13
- class << self
14
- attr_accessor :memory_cache
12
+ attr_reader :fixture
15
13
 
16
- def clear_memory_cache(fixture_name = nil)
17
- if fixture_name
18
- @memory_cache.delete(fixture_name)
19
- else
20
- @memory_cache.clear
21
- end
22
- end
23
-
24
- # Clear fixture cache (both memory and disk)
25
- def clear(fixture_name = nil)
26
- clear_memory_cache(fixture_name)
27
-
28
- cache_path = FixtureKit.configuration.cache_path
29
- if fixture_name
30
- cache_file = File.join(cache_path, "#{fixture_name}.json")
31
- FileUtils.rm_f(cache_file)
32
- else
33
- FileUtils.rm_rf(cache_path)
34
- end
35
- end
36
-
37
- # Generate caches for all fixtures.
38
- # Each fixture is generated in a transaction that rolls back, so no data persists.
39
- def generate_all
40
- Registry.load_definitions
41
- Registry.fixtures.each { |fixture| generate(fixture.name) }
42
- end
43
-
44
- def generate(fixture_name)
45
- clear(fixture_name)
46
-
47
- FixtureKit.configuration.generator.run do
48
- Runner.run(fixture_name, force: true)
49
- end
50
- end
51
- end
52
-
53
- attr_reader :records, :exposed
54
-
55
- def initialize(fixture_name)
56
- @fixture_name = fixture_name
57
- @records = {}
58
- @exposed = {}
14
+ def initialize(fixture, definition)
15
+ @fixture = fixture
16
+ @definition = definition
59
17
  end
60
18
 
61
- def cache_file_path
62
- cache_path = FixtureKit.configuration.cache_path
63
- File.join(cache_path, "#{@fixture_name}.json")
19
+ def path
20
+ File.join(configuration.cache_path, "#{fixture.name}.json")
64
21
  end
65
22
 
66
23
  def exists?
67
- # Check in-memory cache first, then disk
68
- self.class.memory_cache.key?(@fixture_name) || File.exist?(cache_file_path)
24
+ @data || File.exist?(path)
69
25
  end
70
26
 
71
27
  def load
72
- # Check in-memory cache first
73
- if self.class.memory_cache.key?(@fixture_name)
74
- data = self.class.memory_cache[@fixture_name]
75
- @records = data.fetch("records")
76
- @exposed = data.fetch("exposed")
77
- return true
28
+ unless exists?
29
+ raise FixtureKit::CacheMissingError, "Cache does not exist for fixture '#{fixture.name}'"
78
30
  end
79
31
 
80
- # Fall back to disk
81
- return false unless File.exist?(cache_file_path)
82
-
83
- data = JSON.parse(File.read(cache_file_path))
84
- @records = data.fetch("records")
85
- @exposed = data.fetch("exposed")
86
-
87
- # Store in memory for subsequent loads
88
- self.class.memory_cache[@fixture_name] = data
32
+ @data ||= JSON.parse(File.read(path))
33
+ @data.fetch("records").each do |model_name, sql|
34
+ model = ActiveSupport::Inflector.constantize(model_name)
35
+ connection = model.connection
36
+ connection.disable_referential_integrity do
37
+ # execute_batch is private in current supported Rails versions.
38
+ # This should be revisited when Rails 8.2 makes it public.
39
+ connection.__send__(:execute_batch, [build_delete_sql(model), sql].compact, "FixtureKit Load")
40
+ end
41
+ end
89
42
 
90
- true
43
+ build_repository(@data.fetch("exposed"))
91
44
  end
92
45
 
93
- def save(models_with_connections:, exposed_mapping:)
94
- @records = generate_statements(models_with_connections)
95
- @exposed = exposed_mapping
96
-
97
- FileUtils.mkdir_p(File.dirname(cache_file_path))
98
-
99
- data = {
100
- "records" => @records,
101
- "exposed" => @exposed
102
- }
46
+ def save
47
+ FixtureKit.runner.isolator.run do
48
+ models = SqlSubscriber.capture do
49
+ @definition.evaluate
50
+ end
103
51
 
104
- # Store in memory cache
105
- self.class.memory_cache[@fixture_name] = data
52
+ @data = {
53
+ "records" => generate_statements(models),
54
+ "exposed" => build_exposed_mapping(@definition.exposed)
55
+ }
56
+ end
106
57
 
107
- File.write(cache_file_path, JSON.pretty_generate(data))
58
+ FileUtils.mkdir_p(File.dirname(path))
59
+ File.write(path, JSON.pretty_generate(@data))
108
60
  end
109
61
 
110
62
  # Query exposed records from the database and return a Repository.
111
- def build_repository
112
- exposed_records = @exposed.each_with_object({}) do |(name, value), hash|
63
+ def build_repository(exposed)
64
+ exposed_records = exposed.each_with_object({}) do |(name, value), hash|
113
65
  was_array = value.is_a?(Array)
114
66
  records = Array.wrap(value).map { |record_info| find_exposed_record(record_info.fetch("model"), record_info.fetch("id"), name) }
115
67
  hash[name.to_sym] = was_array ? records : records.first
@@ -125,54 +77,47 @@ module FixtureKit
125
77
  model.find(id)
126
78
  rescue ActiveRecord::RecordNotFound
127
79
  raise FixtureKit::ExposedRecordNotFound,
128
- "Could not find #{model_name} with id=#{id} for exposed record '#{exposed_name}' in fixture '#{@fixture_name}'"
80
+ "Could not find #{model_name} with id=#{id} for exposed record '#{exposed_name}' in fixture '#{@fixture.name}'"
129
81
  end
130
82
 
131
- def generate_statements(models_with_connections)
83
+ def generate_statements(models)
132
84
  statements_by_model = {}
133
85
 
134
- models_with_connections.each do |model, connection|
86
+ models.each do |model|
135
87
  columns = model.column_names
136
88
 
137
89
  rows = []
138
90
  model.order(:id).find_each do |record|
139
91
  row_values = columns.map do |col|
140
92
  value = record.read_attribute_before_type_cast(col)
141
- connection.quote(value)
93
+ model.connection.quote(value)
142
94
  end
143
95
  rows << "(#{row_values.join(", ")})"
144
96
  end
145
97
 
146
- next if rows.empty?
147
-
148
- sql = build_insert_sql(model.table_name, columns, rows, connection)
98
+ sql = rows.empty? ? nil : build_insert_sql(model.table_name, columns, rows, model.connection)
149
99
  statements_by_model[model.name] = sql
150
100
  end
151
101
 
152
102
  statements_by_model
153
103
  end
154
104
 
105
+ def build_delete_sql(model)
106
+ "DELETE FROM #{model.quoted_table_name}"
107
+ end
108
+
155
109
  def build_insert_sql(table_name, columns, rows, connection)
156
110
  quoted_table = connection.quote_table_name(table_name)
157
111
  quoted_columns = columns.map { |c| connection.quote_column_name(c) }
158
112
 
159
- sql = "INSERT INTO #{quoted_table} (#{quoted_columns.join(", ")}) VALUES #{rows.join(", ")}"
160
-
161
- add_conflict_handling(sql, connection)
113
+ "INSERT INTO #{quoted_table} (#{quoted_columns.join(", ")}) VALUES #{rows.join(", ")}"
162
114
  end
163
115
 
164
- def add_conflict_handling(sql, connection)
165
- adapter_name = connection.adapter_name.downcase
166
-
167
- case adapter_name
168
- when /sqlite/
169
- sql.sub(/\AINSERT INTO/i, "INSERT OR IGNORE INTO")
170
- when /postgresql/, /postgis/
171
- "#{sql} ON CONFLICT DO NOTHING"
172
- when /mysql/, /trilogy/
173
- sql.sub(/\AINSERT INTO/i, "INSERT IGNORE INTO")
174
- else
175
- sql
116
+ def build_exposed_mapping(exposed)
117
+ exposed.each_with_object({}) do |(name, value), hash|
118
+ was_array = value.is_a?(Array)
119
+ records = Array.wrap(value).map { |record| { "model" => record.class.name, "id" => record.id } }
120
+ hash[name] = was_array ? records : records.first
176
121
  end
177
122
  end
178
123
  end
@@ -4,18 +4,18 @@ module FixtureKit
4
4
  class Configuration
5
5
  attr_writer :fixture_path
6
6
  attr_writer :cache_path
7
- attr_accessor :generator
8
- attr_accessor :autogenerate
7
+ attr_accessor :isolator
8
+ attr_accessor :on_cache
9
9
 
10
10
  def initialize
11
- @fixture_path = nil
11
+ @fixture_path = "fixture_kit"
12
12
  @cache_path = nil
13
- @generator = FixtureKit::TestCase::Generator
14
- @autogenerate = true
13
+ @isolator = FixtureKit::MinitestIsolator
14
+ @on_cache = nil
15
15
  end
16
16
 
17
17
  def fixture_path
18
- @fixture_path ||= detect_fixture_path
18
+ @fixture_path
19
19
  end
20
20
 
21
21
  def cache_path
@@ -24,20 +24,6 @@ module FixtureKit
24
24
 
25
25
  private
26
26
 
27
- def detect_fixture_path
28
- if defined?(RSpec)
29
- "spec/fixture_kit"
30
- elsif defined?(Minitest)
31
- "test/fixture_kit"
32
- elsif Dir.exist?("spec")
33
- "spec/fixture_kit"
34
- elsif Dir.exist?("test")
35
- "test/fixture_kit"
36
- else
37
- "spec/fixture_kit"
38
- end
39
- end
40
-
41
27
  def detect_cache_path
42
28
  "tmp/cache/fixture_kit"
43
29
  end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FixtureKit
4
+ module ConfigurationHelper
5
+ private
6
+
7
+ def configuration
8
+ FixtureKit.runner.configuration
9
+ end
10
+ end
11
+ end
@@ -1,23 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FixtureKit
4
- class DefinitionContext
4
+ class Definition
5
5
  attr_reader :exposed
6
6
 
7
- def initialize
7
+ def initialize(&definition)
8
+ @definition = definition
8
9
  @exposed = {}
9
10
  end
10
11
 
12
+ def evaluate
13
+ instance_eval(&@definition)
14
+ end
15
+
11
16
  def expose(**records)
12
17
  records.each do |name, record|
13
- name = name.to_sym
14
-
15
18
  if @exposed.key?(name)
16
- raise FixtureKit::DuplicateNameError, <<~ERROR
17
- Duplicate expose name :#{name}
18
-
19
- A record with this name has already been exposed in this fixture.
20
- ERROR
19
+ raise FixtureKit::DuplicateNameError, "Name #{name} already exposed"
21
20
  end
22
21
 
23
22
  @exposed[name] = record
@@ -2,17 +2,39 @@
2
2
 
3
3
  module FixtureKit
4
4
  class Fixture
5
- attr_reader :name, :block
5
+ include ConfigurationHelper
6
6
 
7
- def initialize(name, &block)
7
+ attr_reader :name, :path
8
+
9
+ def initialize(name, path)
8
10
  @name = name
9
- @block = block
11
+ @path = path
12
+ @cache = Cache.new(self, definition)
13
+ end
14
+
15
+ def cache(force: false)
16
+ return if @cache.exists? && !force
17
+
18
+ configuration.on_cache&.call(name)
19
+ @cache.save
10
20
  end
11
21
 
12
- def execute
13
- context = DefinitionContext.new
14
- context.instance_eval(&block) if block
15
- context.exposed
22
+ def mount
23
+ unless @cache.exists?
24
+ raise FixtureKit::CacheMissingError, "Cache does not exist for fixture '#{name}'"
25
+ end
26
+
27
+ @cache.load
28
+ end
29
+
30
+ private
31
+
32
+ def definition
33
+ @definition ||= begin
34
+ definition = eval(File.read(@path), TOPLEVEL_BINDING.dup, @path)
35
+ raise FixtureKit::FixtureDefinitionNotFound, "Could not find fixture definition at '#{@path}'" unless definition.is_a?(Definition)
36
+ definition
37
+ end
16
38
  end
17
39
  end
18
40
  end
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FixtureKit
4
- # Base class for fixture cache generators.
5
- class Generator
4
+ # Base class for fixture cache isolators.
5
+ class Isolator
6
6
  def self.run(&block)
7
7
  new.run(&block)
8
8
  end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/test_case"
4
+ require "active_record/fixtures"
5
+
6
+ module FixtureKit
7
+ class MinitestIsolator < FixtureKit::Isolator
8
+ TEST_NAME = "fixture kit cache pregeneration"
9
+
10
+ def run(&block)
11
+ test_class = build_test_class
12
+ test_method = test_class.test(TEST_NAME) do
13
+ block.call
14
+ pass
15
+ end
16
+
17
+ result = test_class.new(test_method).run
18
+ return if result.passed?
19
+
20
+ raise result.failures.first.error
21
+ end
22
+
23
+ private
24
+
25
+ def build_test_class
26
+ Class.new(ActiveSupport::TestCase) do
27
+ ::Minitest::Runnable.runnables.delete(self)
28
+ include(::ActiveRecord::TestFixtures)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FixtureKit
4
+ class RSpecIsolator < FixtureKit::Isolator
5
+ def run(&block)
6
+ previous_example = ::RSpec.current_example
7
+ previous_scope = ::RSpec.current_scope
8
+ example_group = build_example_group
9
+ example = example_group.example { block.call }
10
+ instance = example_group.new
11
+ succeeded =
12
+ begin
13
+ example.run(instance, ::RSpec::Core::NullReporter)
14
+ ensure
15
+ ::RSpec.current_example = previous_example
16
+ ::RSpec.current_scope = previous_scope
17
+ end
18
+
19
+ raise example.exception unless succeeded
20
+ end
21
+
22
+ private
23
+
24
+ def build_example_group
25
+ ::RSpec::Core::ExampleGroup.subclass(
26
+ ::RSpec::Core::ExampleGroup,
27
+ "FixtureKit",
28
+ [],
29
+ []
30
+ )
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fixture_kit"
4
+ require "active_support/lazy_load_hooks"
5
+
6
+ module FixtureKit
7
+ module Minitest
8
+ DECLARATION_CLASS_ATTRIBUTE = :fixture_kit_declaration
9
+
10
+ module ClassMethods
11
+ def fixture(name)
12
+ self.fixture_kit_declaration = FixtureKit.runner.register(name)
13
+ end
14
+ end
15
+
16
+ module InstanceMethods
17
+ def fixture
18
+ @_fixture_kit_repository || raise("No fixture declared for this test class. Use `fixture \"name\"` in your test class.")
19
+ end
20
+ end
21
+
22
+ def self.configure!(test_case)
23
+ FixtureKit.runner.configuration.fixture_path = "test/fixture_kit"
24
+ FixtureKit.runner.configuration.isolator = FixtureKit::MinitestIsolator
25
+
26
+ test_case.class_attribute DECLARATION_CLASS_ATTRIBUTE, instance_accessor: false
27
+ test_case.extend ClassMethods
28
+ test_case.include InstanceMethods
29
+
30
+ test_case.setup do
31
+ declaration = self.class.fixture_kit_declaration
32
+ next unless declaration
33
+
34
+ FixtureKit.runner.start unless FixtureKit.runner.started?
35
+ @_fixture_kit_repository = declaration.mount
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ ActiveSupport.on_load(:active_support_test_case) do
42
+ FixtureKit::Minitest.configure!(self)
43
+ end
@@ -1,58 +1,35 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module FixtureKit
4
- module Registry
5
- class << self
6
- def fetch(name)
7
- fixture = find(name)
8
- return fixture if fixture
9
-
10
- file_path = fixture_file_path(name)
11
- unless File.file?(file_path)
12
- raise FixtureKit::FixtureDefinitionNotFound,
13
- "Could not find fixture definition file for '#{name}' at '#{file_path}'"
14
- end
15
-
16
- load file_path
17
- find(name)
18
- end
3
+ require "pathname"
19
4
 
20
- def find(name)
21
- registry[name]
22
- end
5
+ module FixtureKit
6
+ class Registry
7
+ include ConfigurationHelper
23
8
 
24
- def fixtures
25
- registry.values
26
- end
9
+ def initialize
10
+ @registry = {}
11
+ end
27
12
 
28
- def register(fixture)
29
- registry[fixture.name] = fixture
30
- end
13
+ def add(name)
14
+ return @registry[name] if @registry.key?(name)
31
15
 
32
- # Load all fixture definition files.
33
- # Uses `load` instead of `require` to ensure fixtures are registered
34
- # even if the files were previously required (e.g., after a reset).
35
- def load_definitions
36
- fixture_path = FixtureKit.configuration.fixture_path
37
- Dir.glob(File.join(fixture_path, "**/*.rb")).each do |file|
38
- load file
39
- end
16
+ file_path = fixture_file_path(name)
17
+ unless File.file?(file_path)
18
+ raise FixtureKit::FixtureDefinitionNotFound,
19
+ "Could not find fixture definition file for '#{name}' at '#{file_path}'"
40
20
  end
41
21
 
42
- def reset
43
- @registry = nil
44
- end
22
+ @registry[name] = Fixture.new(name, file_path)
23
+ end
45
24
 
46
- private
25
+ def fixtures
26
+ @registry.values
27
+ end
47
28
 
48
- def fixture_file_path(name)
49
- fixture_path = FixtureKit.configuration.fixture_path
50
- File.expand_path(File.join(fixture_path, "#{name}.rb"))
51
- end
29
+ private
52
30
 
53
- def registry
54
- @registry ||= {}
55
- end
31
+ def fixture_file_path(name)
32
+ File.expand_path(File.join(configuration.fixture_path, "#{name}.rb"))
56
33
  end
57
34
  end
58
35
  end
@@ -4,11 +4,7 @@ require "fixture_kit"
4
4
 
5
5
  module FixtureKit
6
6
  module RSpec
7
- autoload :Declaration, File.expand_path("rspec/declaration", __dir__)
8
- autoload :Generator, File.expand_path("rspec/generator", __dir__)
9
-
10
7
  DECLARATION_METADATA_KEY = :fixture_kit_declaration
11
- PRESERVE_CACHE_ENV_KEY = "FIXTURE_KIT_PRESERVE_CACHE"
12
8
 
13
9
  # Class methods (extended via config.extend)
14
10
  module ClassMethods
@@ -32,7 +28,7 @@ module FixtureKit
32
28
  # end
33
29
  # end
34
30
  def fixture(name)
35
- metadata[DECLARATION_METADATA_KEY] = Declaration.new(name)
31
+ metadata[DECLARATION_METADATA_KEY] = ::RSpec.configuration.fixture_kit.register(name)
36
32
  end
37
33
  end
38
34
 
@@ -41,46 +37,28 @@ module FixtureKit
41
37
  # Returns the Repository for the current example's fixture.
42
38
  # Access exposed records as methods: fixture.alice, fixture.posts
43
39
  def fixture
44
- @_fixture_kit_fixture_set || raise("No fixture declared for this example group. Use `fixture \"name\"` in your describe/context block.")
40
+ @_fixture_kit_repository || raise("No fixture declared for this example group. Use `fixture \"name\"` in your describe/context block.")
45
41
  end
46
42
  end
47
43
 
48
44
  def self.configure!(config)
49
- FixtureKit.configuration.generator = Generator
45
+ FixtureKit.runner.configuration.fixture_path = "spec/fixture_kit"
46
+ config.add_setting(:fixture_kit, default: FixtureKit.runner)
47
+ FixtureKit.runner.configuration.isolator = FixtureKit::RSpecIsolator
48
+
50
49
  config.extend ClassMethods
51
- config.include InstanceMethods
50
+ config.include InstanceMethods, DECLARATION_METADATA_KEY
52
51
 
53
52
  # Load declared fixtures at the beginning of each example.
54
53
  # Runs inside transactional fixtures and before user-defined before hooks.
55
54
  config.prepend_before(:example, DECLARATION_METADATA_KEY) do |example|
56
- declaration = example.metadata[DECLARATION_METADATA_KEY]
57
- @_fixture_kit_fixture_set = declaration.fixture_set
55
+ @_fixture_kit_repository = example.metadata[DECLARATION_METADATA_KEY].mount
58
56
  end
59
57
 
60
- # Setup caches at suite start only when at least one fixture-backed
61
- # example exists in the loaded suite.
62
- config.when_first_matching_example_defined(DECLARATION_METADATA_KEY) do
63
- config.before(:suite) do
64
- if FixtureKit.configuration.autogenerate
65
- preserve_cache = ENV[PRESERVE_CACHE_ENV_KEY].to_s.match?(/\A(1|true|yes)\z/i)
66
- Cache.clear unless preserve_cache
67
- else
68
- fixture_names_for_loaded_examples.each do |fixture_name|
69
- Cache.generate(fixture_name)
70
- end
71
- end
72
- end
58
+ config.append_before(:suite) do
59
+ config.fixture_kit.start
73
60
  end
74
61
  end
75
-
76
- def self.fixture_names_for_loaded_examples
77
- ::RSpec.world.filtered_examples.each_value.with_object(Set.new) do |examples, names|
78
- examples.each do |example|
79
- declaration = example.metadata[DECLARATION_METADATA_KEY]
80
- names << declaration.name if declaration
81
- end
82
- end.to_a
83
- end
84
62
  end
85
63
  end
86
64
 
@@ -1,93 +1,50 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/inflector"
3
+ require "fileutils"
4
4
 
5
5
  module FixtureKit
6
6
  class Runner
7
- def self.run(fixture_name, force: false)
8
- new(fixture_name).run(force: force)
9
- end
10
-
11
- def initialize(fixture_name)
12
- @fixture_name = fixture_name
13
- @cache = Cache.new(@fixture_name)
14
- end
7
+ PRESERVE_CACHE_ENV_KEY = "FIXTURE_KIT_PRESERVE_CACHE"
15
8
 
16
- def run(force: false)
17
- if force
18
- execute_and_cache
19
- elsif @cache.exists?
20
- execute_from_cache
21
- elsif FixtureKit.configuration.autogenerate
22
- execute_and_cache
23
- else
24
- raise FixtureKit::CacheMissingError, <<~ERROR
25
- Cache not found for fixture '#{@fixture_name}'.
9
+ attr_reader :configuration, :registry
26
10
 
27
- Run your tests with autogenerate enabled to generate the cache:
28
- FixtureKit.configuration.autogenerate = true
11
+ def initialize
12
+ @configuration = Configuration.new
13
+ @registry = Registry.new
14
+ @isolator = nil
15
+ @started = false
16
+ end
29
17
 
30
- Or generate caches by running your test suite once with autogenerate enabled.
31
- ERROR
18
+ def register(name)
19
+ registry.add(name).tap do |fixture|
20
+ fixture.cache if started?
32
21
  end
33
22
  end
34
23
 
35
- private
36
-
37
- def execute_and_cache
38
- fixture = Registry.fetch(@fixture_name)
39
-
40
- # Start capturing SQL
41
- capture = SqlCapture.new
42
- capture.start
43
-
44
- # Execute fixture definition - returns exposed records hash
45
- exposed = fixture.execute
46
-
47
- # Stop capturing and get affected models with their connections
48
- models_with_connections = capture.stop
24
+ def start
25
+ raise RunnerAlreadyStartedError, "FixtureKit::Runner has already been started" if started?
26
+ @started = true
49
27
 
50
- # Save cache
51
- @cache.save(
52
- models_with_connections: models_with_connections,
53
- exposed_mapping: build_exposed_mapping(exposed)
54
- )
55
-
56
- # Return Repository from the exposed records
57
- Repository.new(exposed)
28
+ clear_cache unless preserve_cache?
29
+ registry.fixtures.each(&:cache)
58
30
  end
59
31
 
60
- def execute_from_cache
61
- @cache.load
62
-
63
- # Execute cached SQL statements by model
64
- @cache.records.each do |model_name, sql|
65
- next if sql.nil? || sql.empty?
66
-
67
- model = ActiveSupport::Inflector.constantize(model_name)
68
- connection = model.connection
69
- connection.execute(sql)
70
- end
32
+ def isolator
33
+ @isolator ||= configuration.isolator.new
34
+ end
71
35
 
72
- # Query exposed records and build Repository.
73
- @cache.build_repository
36
+ def started?
37
+ @started
74
38
  end
75
39
 
76
- def build_exposed_mapping(exposed)
77
- mapping = {}
40
+ private
78
41
 
79
- exposed.each do |name, record_or_records|
80
- if record_or_records.is_a?(Array)
81
- mapping[name.to_s] = record_or_records.map do |record|
82
- { "model" => record.class.name, "id" => record.id }
83
- end
84
- else
85
- record = record_or_records
86
- mapping[name.to_s] = { "model" => record.class.name, "id" => record.id }
87
- end
88
- end
42
+ def clear_cache
43
+ FileUtils.rm_rf(configuration.cache_path)
44
+ end
89
45
 
90
- mapping
46
+ def preserve_cache?
47
+ ENV[PRESERVE_CACHE_ENV_KEY].to_s.match?(/\A(1|true|yes)\z/i)
91
48
  end
92
49
  end
93
50
  end
@@ -1,35 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "pathname"
4
-
5
3
  module FixtureKit
6
4
  module Singleton
7
5
  def configure
8
- yield(configuration) if block_given?
6
+ yield(runner.configuration) if block_given?
9
7
  self
10
8
  end
11
9
 
12
- def configuration
13
- @configuration ||= Configuration.new
10
+ def runner
11
+ @runner ||= Runner.new
14
12
  end
15
13
 
16
14
  def define(&block)
17
- caller_file = File.expand_path(caller_locations(1, 1).first.path)
18
- fixture_path = File.expand_path(configuration.fixture_path)
19
-
20
- # "/abs/path/spec/fixtures/teams/basic.rb" -> "teams/basic"
21
- relative_path = Pathname.new(caller_file).relative_path_from(Pathname.new(fixture_path))
22
- name = relative_path.to_s.sub(/\.rb$/, "")
23
-
24
- fixture = Fixture.new(name, &block)
25
- Registry.register(fixture)
26
- fixture
15
+ Definition.new(&block)
27
16
  end
28
17
 
29
18
  def reset
30
- @configuration = nil
31
- Registry.reset
32
- Cache.clear_memory_cache
19
+ @runner = nil
33
20
  end
34
21
  end
35
22
  end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/notifications"
4
+ require "active_support/inflector"
5
+
6
+ module FixtureKit
7
+ class SqlSubscriber
8
+ EVENT = "sql.active_record"
9
+ NAME_PATTERN = /\A(?<model_name>.+?) (?:(?:Bulk )?(?:Insert|Upsert)|Create|Destroy|(?:Update|Delete)(?: All)?)\z/
10
+
11
+ def self.capture(&block)
12
+ models = Set.new
13
+ subscriber = lambda do |_event_name, _start, _finish, _id, payload|
14
+ name = payload[:name].to_s
15
+ model_name = name[NAME_PATTERN, :model_name]
16
+ next unless model_name
17
+
18
+ models.add(ActiveSupport::Inflector.constantize(model_name))
19
+ end
20
+
21
+ ActiveSupport::Notifications.subscribed(subscriber, EVENT, monotonic: true, &block)
22
+
23
+ models.to_a
24
+ end
25
+ end
26
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FixtureKit
4
- VERSION = "0.2.0"
4
+ VERSION = "0.4.0"
5
5
  end
data/lib/fixture_kit.rb CHANGED
@@ -2,25 +2,26 @@
2
2
 
3
3
  module FixtureKit
4
4
  class Error < StandardError; end
5
- class DuplicateFixtureError < Error; end
6
5
  class DuplicateNameError < Error; end
7
6
  class CacheMissingError < Error; end
8
- class PregenerationError < Error; end
9
7
  class FixtureDefinitionNotFound < Error; end
10
8
  class ExposedRecordNotFound < Error; end
9
+ class RunnerAlreadyStartedError < Error; end
11
10
 
12
- autoload :VERSION, File.expand_path("fixture_kit/version", __dir__)
13
- autoload :Configuration, File.expand_path("fixture_kit/configuration", __dir__)
14
- autoload :Singleton, File.expand_path("fixture_kit/singleton", __dir__)
15
- autoload :Fixture, File.expand_path("fixture_kit/fixture", __dir__)
16
- autoload :DefinitionContext, File.expand_path("fixture_kit/definition_context", __dir__)
17
- autoload :Registry, File.expand_path("fixture_kit/registry", __dir__)
18
- autoload :Repository, File.expand_path("fixture_kit/repository", __dir__)
19
- autoload :SqlCapture, File.expand_path("fixture_kit/sql_capture", __dir__)
20
- autoload :Cache, File.expand_path("fixture_kit/cache", __dir__)
21
- autoload :Runner, File.expand_path("fixture_kit/runner", __dir__)
22
- autoload :Generator, File.expand_path("fixture_kit/generator", __dir__)
23
- autoload :TestCase, File.expand_path("fixture_kit/test_case", __dir__)
11
+ autoload :VERSION, File.expand_path("fixture_kit/version", __dir__)
12
+ autoload :Configuration, File.expand_path("fixture_kit/configuration", __dir__)
13
+ autoload :ConfigurationHelper, File.expand_path("fixture_kit/configuration_helper", __dir__)
14
+ autoload :Singleton, File.expand_path("fixture_kit/singleton", __dir__)
15
+ autoload :Fixture, File.expand_path("fixture_kit/fixture", __dir__)
16
+ autoload :Definition, File.expand_path("fixture_kit/definition", __dir__)
17
+ autoload :Registry, File.expand_path("fixture_kit/registry", __dir__)
18
+ autoload :Repository, File.expand_path("fixture_kit/repository", __dir__)
19
+ autoload :SqlSubscriber, File.expand_path("fixture_kit/sql_subscriber", __dir__)
20
+ autoload :Cache, File.expand_path("fixture_kit/cache", __dir__)
21
+ autoload :Runner, File.expand_path("fixture_kit/runner", __dir__)
22
+ autoload :Isolator, File.expand_path("fixture_kit/isolator", __dir__)
23
+ autoload :MinitestIsolator, File.expand_path("fixture_kit/isolators/minitest_isolator", __dir__)
24
+ autoload :RSpecIsolator, File.expand_path("fixture_kit/isolators/rspec_isolator", __dir__)
24
25
 
25
26
  extend Singleton
26
27
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fixture_kit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ngan Pham
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-02-21 00:00:00.000000000 Z
11
+ date: 2026-02-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -136,19 +136,19 @@ files:
136
136
  - lib/fixture_kit.rb
137
137
  - lib/fixture_kit/cache.rb
138
138
  - lib/fixture_kit/configuration.rb
139
- - lib/fixture_kit/definition_context.rb
139
+ - lib/fixture_kit/configuration_helper.rb
140
+ - lib/fixture_kit/definition.rb
140
141
  - lib/fixture_kit/fixture.rb
141
- - lib/fixture_kit/generator.rb
142
+ - lib/fixture_kit/isolator.rb
143
+ - lib/fixture_kit/isolators/minitest_isolator.rb
144
+ - lib/fixture_kit/isolators/rspec_isolator.rb
145
+ - lib/fixture_kit/minitest.rb
142
146
  - lib/fixture_kit/registry.rb
143
147
  - lib/fixture_kit/repository.rb
144
148
  - lib/fixture_kit/rspec.rb
145
- - lib/fixture_kit/rspec/declaration.rb
146
- - lib/fixture_kit/rspec/generator.rb
147
149
  - lib/fixture_kit/runner.rb
148
150
  - lib/fixture_kit/singleton.rb
149
- - lib/fixture_kit/sql_capture.rb
150
- - lib/fixture_kit/test_case.rb
151
- - lib/fixture_kit/test_case/generator.rb
151
+ - lib/fixture_kit/sql_subscriber.rb
152
152
  - lib/fixture_kit/version.rb
153
153
  homepage: https://github.com/Gusto/fixture_kit
154
154
  licenses:
@@ -1,17 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module FixtureKit
4
- module RSpec
5
- class Declaration
6
- attr_reader :name
7
-
8
- def initialize(name)
9
- @name = name
10
- end
11
-
12
- def fixture_set
13
- FixtureKit::Runner.run(name)
14
- end
15
- end
16
- end
17
- end
@@ -1,45 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module FixtureKit
4
- module RSpec
5
- class Generator < FixtureKit::Generator
6
- def run(&block)
7
- previous_example = ::RSpec.current_example
8
- previous_scope = ::RSpec.current_scope
9
- example_group = build_example_group
10
- example = build_example(example_group, &block)
11
- instance = example_group.new(example.inspect_output)
12
- succeeded =
13
- begin
14
- example.run(instance, ::RSpec::Core::NullReporter)
15
- ensure
16
- example_group.remove_example(example)
17
- ::RSpec.current_example = previous_example
18
- ::RSpec.current_scope = previous_scope
19
- end
20
-
21
- unless succeeded
22
- raise example.exception if example.exception
23
- raise FixtureKit::PregenerationError, "FixtureKit pregeneration failed"
24
- end
25
- end
26
-
27
- private
28
-
29
- def build_example(example_group, &block)
30
- example_group.example(
31
- "FixtureKit cache pregeneration"
32
- ) { block.call }
33
- end
34
-
35
- def build_example_group
36
- ::RSpec::Core::ExampleGroup.subclass(
37
- ::RSpec::Core::ExampleGroup,
38
- "FixtureKit::RSpec::Generator",
39
- [],
40
- []
41
- )
42
- end
43
- end
44
- end
45
- end
@@ -1,37 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "active_support/notifications"
4
- require "active_support/inflector"
5
-
6
- module FixtureKit
7
- class SqlCapture
8
- SQL_EVENT = "sql.active_record"
9
-
10
- def initialize
11
- @models = {} # { Model => connection }
12
- @subscription = nil
13
- end
14
-
15
- def start
16
- @subscription = ActiveSupport::Notifications.subscribe(SQL_EVENT) do |*, payload|
17
- next unless payload[:sql] =~ /\AINSERT INTO/i
18
-
19
- # payload[:name] is like "User Create" - extract model name
20
- name = payload[:name]
21
- next unless name&.end_with?(" Create")
22
-
23
- model_name = name.sub(/ Create\z/, "")
24
- model = ActiveSupport::Inflector.constantize(model_name)
25
- @models[model] ||= payload[:connection]
26
- rescue NameError
27
- # Skip if model can't be found
28
- end
29
- end
30
-
31
- def stop
32
- ActiveSupport::Notifications.unsubscribe(@subscription) if @subscription
33
- @subscription = nil
34
- @models
35
- end
36
- end
37
- end
@@ -1,37 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "active_support/test_case"
4
- require "active_record/fixtures"
5
-
6
- module FixtureKit
7
- module TestCase
8
- class Generator < FixtureKit::Generator
9
- TEST_METHOD_NAME = "test_fixture_kit_cache_pregeneration"
10
-
11
- def run(&block)
12
- result = build_test_class(&block).run
13
- return if result.passed?
14
-
15
- failure = result.failures.first
16
- raise failure.error if failure.respond_to?(:error)
17
- raise failure if failure
18
-
19
- raise FixtureKit::PregenerationError, "FixtureKit pregeneration failed"
20
- end
21
-
22
- private
23
-
24
- def build_test_class(&block)
25
- Class.new(ActiveSupport::TestCase) do
26
- ::Minitest::Runnable.runnables.delete(self)
27
- include(::ActiveRecord::TestFixtures)
28
-
29
- define_method(TEST_METHOD_NAME) do
30
- block.call
31
- pass
32
- end
33
- end.new(TEST_METHOD_NAME)
34
- end
35
- end
36
- end
37
- end
@@ -1,7 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module FixtureKit
4
- module TestCase
5
- autoload :Generator, File.expand_path("test_case/generator", __dir__)
6
- end
7
- end