fixture_kit 0.1.1 → 0.1.2

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: fb41bba5897198dbdad616b7b1a44c02cb770b8150e9c005051e16a1935f8508
4
- data.tar.gz: 8e5cb1af113f6fd2f56a0aac8727f2a2666a89fe1fe87b9499fc4c6951b94d02
3
+ metadata.gz: 2e0215c05b7885a41bf05f4ecd197b5df0d2bec66a8994b9ffe3c5310523d7c2
4
+ data.tar.gz: 41f8326c8e2b24094d977127da376efcec101471d61a3da009cbb61a8683cce8
5
5
  SHA512:
6
- metadata.gz: c67e3477b6f2e4b8fb8f9d2ba480c2b37fd73fba173178662d7d20d6ce3c7737b483d1308faa7f5f9a60ce9f1a5b27076988b8bfdf48949ff5944b5d39d988a9
7
- data.tar.gz: 054d623d8e2418720ff73c08a3699dd30471511060ed1581d977da215872e9eae902a0d84439499c816004ceebfbf624c2ac2e1bc91b3f0491231c9a191c3fbd
6
+ metadata.gz: 5315136c139de91a4d6a7bf325fdd9710a0583928b1f0dccdee0d8584afc9be050083ee174a0ddc3d538650d8509e4fd764731c4e547d5f9dacc2651690a454a
7
+ data.tar.gz: f7212cae51606a7e9ef8c80f7c27d9a95e6091b02f0873d2786241780bbd2a961d0f94a1e8cb0bc42cdd188276fc56526b2f1cba1136edeba66384824e255ab4
data/README.md CHANGED
@@ -104,6 +104,8 @@ RSpec.describe Book do
104
104
  end
105
105
  ```
106
106
 
107
+ `fixture` returns a `FixtureSet` and exposes records as methods (for example, `fixture.owner`).
108
+
107
109
  ### 3. Configure RSpec
108
110
 
109
111
  ```ruby
@@ -128,14 +130,25 @@ FixtureKit.configure do |config|
128
130
 
129
131
  # Whether to regenerate caches on every run (default: true)
130
132
  config.autogenerate = true
133
+
134
+ # Optional: customize how pregeneration is wrapped.
135
+ # Default is FixtureKit::TestCase::Generator.
136
+ # config.generator = FixtureKit::TestCase::Generator
131
137
  end
132
138
  ```
133
139
 
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.
142
+
134
143
  ### Autogenerate
135
144
 
136
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.
137
146
 
138
- When `autogenerate` is `false`, FixtureKit pre-generates all fixture caches at suite start. This happens in rolled-back transactions so no data persists to the database. Any fixtures that already have caches are skipped. This mode is useful for CI where you want consistent, predictable cache generation.
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.
148
+
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.
150
+
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.
139
152
 
140
153
  ### Preserving Cache Locally
141
154
 
@@ -4,11 +4,13 @@ module FixtureKit
4
4
  class Configuration
5
5
  attr_writer :fixture_path
6
6
  attr_writer :cache_path
7
+ attr_accessor :generator
7
8
  attr_accessor :autogenerate
8
9
 
9
10
  def initialize
10
11
  @fixture_path = nil
11
12
  @cache_path = nil
13
+ @generator = FixtureKit::TestCase::Generator
12
14
  @autogenerate = true
13
15
  end
14
16
 
@@ -27,14 +27,15 @@ module FixtureKit
27
27
  end
28
28
 
29
29
  # Generate cache only (used for pregeneration in before(:suite))
30
- # Wraps execution in ActiveRecord::TestFixtures transactional lifecycle,
31
- # so no data persists across configured writing pools.
30
+ # Wraps execution in the configured generator lifecycle.
31
+ # The default generator uses ActiveRecord::TestFixtures transactions.
32
+ # Entry points (like `fixture_kit/rspec`) can install richer generators.
32
33
  # Always regenerates the cache, even if one exists
33
34
  def generate_cache_only
34
35
  # Clear any existing cache for this fixture
35
36
  FixtureCache.clear(@fixture_name.to_s)
36
37
 
37
- FixtureKit::TransactionalHarness.run do
38
+ FixtureKit.configuration.generator.run do
38
39
  execute_and_cache
39
40
  end
40
41
 
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FixtureKit
4
+ # Base class for fixture cache generators.
5
+ class Generator
6
+ def self.run(&block)
7
+ new.run(&block)
8
+ end
9
+
10
+ def run
11
+ raise NotImplementedError, "#{self.class} must implement #run"
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,17 @@
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.to_s
10
+ end
11
+
12
+ def fixture_set
13
+ FixtureKit::FixtureRegistry.load_fixture(name)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,45 @@
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,9 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "fixture_kit"
4
+ require_relative "rspec/declaration"
5
+ require_relative "rspec/generator"
4
6
 
5
7
  module FixtureKit
6
8
  module RSpec
9
+ DECLARATION_METADATA_KEY = :fixture_kit_declaration
10
+ PRESERVE_CACHE_ENV_KEY = "FIXTURE_KIT_PRESERVE_CACHE"
11
+
7
12
  # Class methods (extended via config.extend)
8
13
  module ClassMethods
9
14
  # Declare which fixture to use for this example group.
@@ -26,7 +31,7 @@ module FixtureKit
26
31
  # end
27
32
  # end
28
33
  def fixture(name)
29
- metadata[:fixture_name] = name.to_s
34
+ metadata[FixtureKit::RSpec::DECLARATION_METADATA_KEY] = FixtureKit::RSpec::Declaration.new(name)
30
35
  end
31
36
  end
32
37
 
@@ -35,28 +40,33 @@ module FixtureKit
35
40
  # Returns the FixtureSet for the current example's fixture.
36
41
  # Access exposed records as methods: fixture.alice, fixture.posts
37
42
  def fixture
38
- @_fixture_loaded ||= begin
39
- fixture_name = self.class.metadata[:fixture_name]
40
- raise "No fixture declared for this example group. Use `fixture \"name\"` in your describe/context block." unless fixture_name
41
-
42
- FixtureKit::FixtureRegistry.load_fixture(fixture_name)
43
- end
43
+ @_fixture_kit_fixture_set || raise("No fixture declared for this example group. Use `fixture \"name\"` in your describe/context block.")
44
44
  end
45
45
  end
46
46
  end
47
47
  end
48
48
 
49
+ # Install the RSpec generator by default for this entrypoint.
50
+ FixtureKit.configuration.generator = FixtureKit::RSpec::Generator
51
+
49
52
  # Configure RSpec integration
50
53
  RSpec.configure do |config|
51
54
  config.extend FixtureKit::RSpec::ClassMethods
52
55
  config.include FixtureKit::RSpec::InstanceMethods
53
56
 
57
+ # Load declared fixtures at the beginning of each example.
58
+ # Runs inside transactional fixtures and before user-defined before hooks.
59
+ config.prepend_before(:example, FixtureKit::RSpec::DECLARATION_METADATA_KEY) do |example|
60
+ declaration = example.metadata[FixtureKit::RSpec::DECLARATION_METADATA_KEY]
61
+ @_fixture_kit_fixture_set = declaration.fixture_set
62
+ end
63
+
54
64
  # Setup caches at suite start based on autogenerate setting
55
65
  # - autogenerate=true: Clear all caches (unless FIXTURE_KIT_PRESERVE_CACHE is set)
56
66
  # - autogenerate=false: Pre-generate all caches so tests don't fail
57
67
  config.before(:suite) do
58
68
  if FixtureKit.configuration.autogenerate
59
- preserve_cache = ENV["FIXTURE_KIT_PRESERVE_CACHE"].to_s.match?(/\A(1|true|yes)\z/i)
69
+ preserve_cache = ENV[FixtureKit::RSpec::PRESERVE_CACHE_ENV_KEY].to_s.match?(/\A(1|true|yes)\z/i)
60
70
  FixtureKit::FixtureCache.clear unless preserve_cache
61
71
  else
62
72
  FixtureKit::FixtureCache.pregenerate_all
@@ -5,8 +5,7 @@ require "pathname"
5
5
  module FixtureKit
6
6
  module Singleton
7
7
  def configure
8
- @configuration = Configuration.new
9
- yield(@configuration) if block_given?
8
+ yield(configuration) if block_given?
10
9
  self
11
10
  end
12
11
 
@@ -0,0 +1,37 @@
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
@@ -0,0 +1,7 @@
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FixtureKit
4
- VERSION = "0.1.1"
4
+ VERSION = "0.1.2"
5
5
  end
data/lib/fixture_kit.rb CHANGED
@@ -5,6 +5,7 @@ module FixtureKit
5
5
  class DuplicateFixtureError < Error; end
6
6
  class DuplicateNameError < Error; end
7
7
  class CacheMissingError < Error; end
8
+ class PregenerationError < Error; end
8
9
  class ExposedRecordNotFound < Error; end
9
10
 
10
11
  autoload :VERSION, File.expand_path("fixture_kit/version", __dir__)
@@ -17,7 +18,8 @@ module FixtureKit
17
18
  autoload :SqlCapture, File.expand_path("fixture_kit/sql_capture", __dir__)
18
19
  autoload :FixtureCache, File.expand_path("fixture_kit/fixture_cache", __dir__)
19
20
  autoload :FixtureRunner, File.expand_path("fixture_kit/fixture_runner", __dir__)
20
- autoload :TransactionalHarness, File.expand_path("fixture_kit/transactional_harness", __dir__)
21
+ autoload :Generator, File.expand_path("fixture_kit/generator", __dir__)
22
+ autoload :TestCase, File.expand_path("fixture_kit/test_case", __dir__)
21
23
 
22
24
  extend Singleton
23
25
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fixture_kit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ngan Pham
@@ -141,10 +141,14 @@ files:
141
141
  - lib/fixture_kit/fixture_registry.rb
142
142
  - lib/fixture_kit/fixture_runner.rb
143
143
  - lib/fixture_kit/fixture_set.rb
144
+ - lib/fixture_kit/generator.rb
144
145
  - lib/fixture_kit/rspec.rb
146
+ - lib/fixture_kit/rspec/declaration.rb
147
+ - lib/fixture_kit/rspec/generator.rb
145
148
  - lib/fixture_kit/singleton.rb
146
149
  - lib/fixture_kit/sql_capture.rb
147
- - lib/fixture_kit/transactional_harness.rb
150
+ - lib/fixture_kit/test_case.rb
151
+ - lib/fixture_kit/test_case/generator.rb
148
152
  - lib/fixture_kit/version.rb
149
153
  homepage: https://github.com/Gusto/fixture_kit
150
154
  licenses:
@@ -1,29 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "active_record/fixtures"
4
-
5
- module FixtureKit
6
- # Runs arbitrary code inside ActiveRecord::TestFixtures lifecycle without
7
- # defining a real test example. This gives us Rails' transactional handling
8
- # across all configured writing pools.
9
- class TransactionalHarness
10
- include ActiveRecord::TestFixtures
11
-
12
- def self.run(&block)
13
- new.run(&block)
14
- end
15
-
16
- # ActiveRecord::TestFixtures checks `name` to decide whether the current
17
- # method is marked with `uses_transaction`.
18
- def name
19
- "fixture_kit_transactional_harness"
20
- end
21
-
22
- def run
23
- setup_fixtures
24
- yield
25
- ensure
26
- teardown_fixtures
27
- end
28
- end
29
- end