Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,9 @@ FixtureKit.configure do |config|
# Where cache files are stored (default: tmp/cache/fixture_kit)
config.cache_path = Rails.root.join("tmp/cache/fixture_kit").to_s

# Wrapper used to isolate generation work (default: FixtureKit::Minitest::Isolator)
# config.isolator = FixtureKit::Minitest::Isolator
# config.isolator = FixtureKit::RSpec::Isolator
# Wrapper used to isolate generation work (default: FixtureKit::MinitestIsolator)
# config.isolator = FixtureKit::MinitestIsolator
# config.isolator = FixtureKit::RSpecIsolator

# Optional callback, called whenever a fixture cache is generated.
# Receives the fixture name as a String.
Expand All @@ -143,9 +143,9 @@ end
Custom isolators should subclass `FixtureKit::Isolator` and implement `#run`.
`#run` receives the generation block and should execute it in whatever lifecycle you need.

By default, FixtureKit uses `FixtureKit::Minitest::Isolator`, which runs generation inside an internal `ActiveSupport::TestCase` and removes that harness case from minitest runnables.
By default, FixtureKit uses `FixtureKit::MinitestIsolator`, which runs generation inside an internal `ActiveSupport::TestCase` and removes that harness case from minitest runnables.

When using `fixture_kit/rspec`, FixtureKit sets `FixtureKit::RSpec::Isolator`. It runs generation inside an internal RSpec example, and uses a null reporter so harness runs do not count toward suite example totals.
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.

## Lifecycle

Expand Down
5 changes: 2 additions & 3 deletions lib/fixture_kit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@

module FixtureKit
class Error < StandardError; end
class DuplicateFixtureError < Error; end
class DuplicateNameError < Error; end
class CacheMissingError < Error; end
class PregenerationError < Error; end
class FixtureDefinitionNotFound < Error; end
class ExposedRecordNotFound < Error; end
class RunnerAlreadyStartedError < Error; end
Expand All @@ -22,7 +20,8 @@ class RunnerAlreadyStartedError < Error; end
autoload :Cache, File.expand_path("fixture_kit/cache", __dir__)
autoload :Runner, File.expand_path("fixture_kit/runner", __dir__)
autoload :Isolator, File.expand_path("fixture_kit/isolator", __dir__)
autoload :Minitest, File.expand_path("fixture_kit/minitest", __dir__)
autoload :MinitestIsolator, File.expand_path("fixture_kit/isolators/minitest_isolator", __dir__)
autoload :RSpecIsolator, File.expand_path("fixture_kit/isolators/rspec_isolator", __dir__)

extend Singleton
end
2 changes: 1 addition & 1 deletion lib/fixture_kit/cache.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def load
end

def save
configuration.isolator.run do
FixtureKit.runner.isolator.run do
models = SqlSubscriber.capture do
@definition.evaluate
end
Expand Down
2 changes: 1 addition & 1 deletion lib/fixture_kit/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class Configuration
def initialize
@fixture_path = nil
@cache_path = nil
@isolator = FixtureKit::Minitest::Isolator
@isolator = FixtureKit::MinitestIsolator
@on_cache = nil
end

Expand Down
32 changes: 32 additions & 0 deletions lib/fixture_kit/isolators/minitest_isolator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# frozen_string_literal: true

require "active_support/test_case"
require "active_record/fixtures"

module FixtureKit
class MinitestIsolator < FixtureKit::Isolator
TEST_NAME = "fixture kit cache pregeneration"

def run(&block)
test_class = build_test_class
test_method = test_class.test(TEST_NAME) do
block.call
pass
end

result = test_class.new(test_method).run
return if result.passed?

raise result.failures.first.error
end

private

def build_test_class
Class.new(ActiveSupport::TestCase) do
::Minitest::Runnable.runnables.delete(self)
include(::ActiveRecord::TestFixtures)
end
end
end
end
33 changes: 33 additions & 0 deletions lib/fixture_kit/isolators/rspec_isolator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# frozen_string_literal: true

module FixtureKit
class RSpecIsolator < FixtureKit::Isolator
def run(&block)
previous_example = ::RSpec.current_example
previous_scope = ::RSpec.current_scope
example_group = build_example_group
example = example_group.example { block.call }
instance = example_group.new
succeeded =
begin
example.run(instance, ::RSpec::Core::NullReporter)
ensure
::RSpec.current_example = previous_example
::RSpec.current_scope = previous_scope
end

raise example.exception unless succeeded
end

private

def build_example_group
::RSpec::Core::ExampleGroup.subclass(
::RSpec::Core::ExampleGroup,
"FixtureKit",
[],
[]
)
end
end
end
6 changes: 1 addition & 5 deletions lib/fixture_kit/minitest.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
# frozen_string_literal: true

module FixtureKit
module Minitest
autoload :Isolator, File.expand_path("minitest/isolator", __dir__)
end
end
require "fixture_kit"
37 changes: 0 additions & 37 deletions lib/fixture_kit/minitest/isolator.rb

This file was deleted.

4 changes: 1 addition & 3 deletions lib/fixture_kit/rspec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@

module FixtureKit
module RSpec
autoload :Isolator, File.expand_path("rspec/isolator", __dir__)

DECLARATION_METADATA_KEY = :fixture_kit_declaration

# Class methods (extended via config.extend)
Expand Down Expand Up @@ -45,7 +43,7 @@ def fixture

def self.configure!(config)
config.add_setting(:fixture_kit, default: FixtureKit.runner)
FixtureKit.runner.configuration.isolator = Isolator
FixtureKit.runner.configuration.isolator = FixtureKit::RSpecIsolator

config.extend ClassMethods
config.include InstanceMethods, DECLARATION_METADATA_KEY
Expand Down
45 changes: 0 additions & 45 deletions lib/fixture_kit/rspec/isolator.rb

This file was deleted.

5 changes: 5 additions & 0 deletions lib/fixture_kit/runner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class Runner
def initialize
@configuration = Configuration.new
@registry = Registry.new
@isolator = nil
@started = false
end

Expand All @@ -28,6 +29,10 @@ def start
registry.fixtures.each(&:cache)
end

def isolator
@isolator ||= configuration.isolator.new
end

def started?
@started
end
Expand Down
10 changes: 10 additions & 0 deletions spec/integration/dummy_app_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
DUMMY_ROOT = File.expand_path("../dummy", __dir__)
DUMMY_RSPEC_PATH = "spec/integration/fixture_kit_integration.rb"
DUMMY_MINITEST_PATH = "test/integration/fixture_kit_integration_test.rb"
EXPECTED_DUMMY_TEST_COUNT = 8
INTEGRATION_FRAMEWORK = ENV.fetch("FIXTURE_KIT_INTEGRATION_FRAMEWORK", "rspec")

def run_dummy_tests
Expand Down Expand Up @@ -69,5 +70,14 @@ def run_dummy_tests
expected_markers.each do |marker|
expect(output).to include(marker), "Expected marker #{marker.inspect} in output.\nOutput:\n#{output}"
end

case INTEGRATION_FRAMEWORK
when "rspec"
expect(output).to match(/\b#{EXPECTED_DUMMY_TEST_COUNT} examples, 0 failures\b/)
when "minitest"
expect(output).to match(/\b#{EXPECTED_DUMMY_TEST_COUNT} runs, \d+ assertions, 0 failures, 0 errors, \d+ skips\b/)
else
raise ArgumentError, "Unsupported integration framework: #{INTEGRATION_FRAMEWORK}"
end
end
end
2 changes: 1 addition & 1 deletion spec/support/dummy_rails_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -101,5 +101,5 @@ def setup_databases
FixtureKit.configure do |config|
config.fixture_path = Rails.root.join("fixture_kit").to_s
config.cache_path = Rails.root.join("tmp/cache/fixture_kit").to_s
config.isolator = FixtureKit::RSpec::Isolator if defined?(FixtureKit::RSpec::Isolator)
config.isolator = FixtureKit::RSpecIsolator if defined?(FixtureKit::RSpecIsolator)
end
4 changes: 2 additions & 2 deletions spec/unit/configuration_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

RSpec.describe FixtureKit::Configuration do
describe "#isolator" do
it "defaults to FixtureKit::Minitest::Isolator" do
expect(described_class.new.isolator).to eq(FixtureKit::Minitest::Isolator)
it "defaults to FixtureKit::MinitestIsolator" do
expect(described_class.new.isolator).to eq(FixtureKit::MinitestIsolator)
end

it "returns an explicitly configured class" do
Expand Down
2 changes: 1 addition & 1 deletion spec/unit/fixture_cache_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

let(:pass_through_isolator) do
Class.new do
def self.run(&block)
def run(&block)
block.call
end
end
Expand Down
16 changes: 16 additions & 0 deletions spec/unit/fixture_runner_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,22 @@
allow(FixtureKit::Registry).to receive(:new).and_return(registry)
end

describe "#isolator" do
it "instantiates the configured isolator once and memoizes it" do
runner = described_class.new
isolator_instance = instance_double(FixtureKit::MinitestIsolator)
isolator_class = class_double(FixtureKit::MinitestIsolator, new: isolator_instance)
runner.configuration.isolator = isolator_class

first_isolator = runner.isolator
second_isolator = runner.isolator

expect(first_isolator).to equal(isolator_instance)
expect(second_isolator).to equal(isolator_instance)
expect(isolator_class).to have_received(:new).once
end
end

describe "#start" do
it "clears all cache files before caching fixtures" do
runner = described_class.new
Expand Down
34 changes: 24 additions & 10 deletions spec/unit/minitest_isolator_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

require "spec_helper"

RSpec.describe FixtureKit::Minitest::Isolator do
RSpec.describe FixtureKit::MinitestIsolator do
describe ".run" do
it "builds an ActiveSupport::TestCase harness" do
captured_test_case_class = nil
Expand All @@ -17,7 +17,23 @@
expect(captured_test_case_class).to be < ActiveSupport::TestCase
expect(captured_test_case_class.included_modules).to include(ActiveRecord::TestFixtures)
expect(captured_test_case_class.public_instance_methods(false).map(&:to_s))
.to include(described_class::TEST_METHOD_NAME)
.to include("test_fixture_kit_cache_pregeneration")
end

it "builds a fresh harness class for each run on the same isolator instance" do
captured_test_case_classes = []

allow(Minitest::Runnable.runnables).to receive(:delete).and_wrap_original do |original, test_case_class|
captured_test_case_classes << test_case_class
original.call(test_case_class)
end

isolator = described_class.new
isolator.run { nil }
isolator.run { nil }

expect(captured_test_case_classes.size).to be >= 2
expect(captured_test_case_classes[-2]).not_to equal(captured_test_case_classes[-1])
end

it "does not leak harness test cases into minitest runnables" do
Expand All @@ -40,16 +56,14 @@
expect(Minitest::Runnable.runnables).to eq(runnables_before)
end

it "raises FixtureKit::PregenerationError when run fails without a failure" do
isolator = described_class.new
result = instance_double(Minitest::Result, passed?: false, failures: [])
test_case = instance_double(ActiveSupport::TestCase, run: result)

allow(described_class).to receive(:new).and_return(isolator)
allow(isolator).to receive(:build_test_class).and_return(test_case)
it "re-raises the first failure error when run fails" do
failure_error = RuntimeError.new("harness exploded")
failure = instance_double(Minitest::UnexpectedError, error: failure_error)
result = instance_double(Minitest::Result, passed?: false, failures: [failure])
allow_any_instance_of(ActiveSupport::TestCase).to receive(:run).and_return(result)

expect { described_class.run { nil } }
.to raise_error(FixtureKit::PregenerationError, "FixtureKit pregeneration failed")
.to raise_error(RuntimeError, "harness exploded")
end
end
end
Loading