From ecbf04d38e626b890e6bca7191b9f02270efa6ed Mon Sep 17 00:00:00 2001 From: Grant Hutchins Date: Thu, 3 Sep 2026 12:56:15 -0500 Subject: [PATCH] Document test runner integrations Add minimal README setup and detailed RDoc-compatible guides for each supported test runner. #68 --- CHANGELOG.md | 6 ++++ CONTRIBUTING.md | 23 +++++++++++++-- README.md | 57 +++++++++++++++++++++++++++++------- docs/minitest.md | 45 ++++++++++++++++++++++++++++ docs/rspec.md | 43 +++++++++++++++++++++++++++ docs/test-unit.md | 73 ++++++++++++++++++++++++++++++++++++++++++++++ with_model.gemspec | 3 +- 7 files changed, 236 insertions(+), 14 deletions(-) create mode 100644 docs/minitest.md create mode 100644 docs/rspec.md create mode 100644 docs/test-unit.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a59c67..9134165 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ ### Unreleased +- Add Test::Unit support, including lifecycle integration for dynamic models and + tables. +- Add `with_model/rspec`, `with_model/minitest`, and `with_model/test_unit` + convenience entrypoints. +- Add runner integration guides for RSpec, Minitest, and Test::Unit. +- Skip temporary-table teardown when the table has already disappeared. - Require Active Record 8.0 or later. ### 2.3.1 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 224e832..05e1dd2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,12 +24,22 @@ avoid anything that only works on the newest of either. ## Running the tests -The suite runs against both supported test harnesses, so `with_model` has to work -under each: +The suite runs against all three supported test harnesses, so `with_model` has +to work under each: - `spec/` covers behavior under RSpec. -- `test/` covers the minitest life cycle — the setup and teardown hooks, and +- `test/` covers the Minitest life cycle — the setup and teardown hooks, and their ordering — rather than repeating the specs. +- `test_unit/` covers Test::Unit integration in a dedicated process, including + native fixture callback ordering. + +Run one harness with its scoped Rake task: + +```sh +bundle exec rake spec +bundle exec rake test +bundle exec rake test_unit +``` Run everything, including the linter, with: @@ -37,6 +47,13 @@ Run everything, including the linter, with: bundle exec rake ``` +Validate the README and runner guides with RDoc before submitting documentation +changes: + +```sh +rm -rf tmp/rdoc && bundle exec rdoc --markup markdown --op tmp/rdoc README.md docs/rspec.md docs/minitest.md docs/test-unit.md lib/**/*.rb +``` + `bin/` holds generated binstubs and is not checked in. Whether bundling writes them for you depends on your Bundler version, so create them once if you would rather type `bin/rake`: diff --git a/README.md b/README.md index efc529a..696e034 100644 --- a/README.md +++ b/README.md @@ -14,32 +14,63 @@ Install as usual: `gem install with_model` or add `gem 'with_model'` to your Gemfile. See [`.github/workflows/ci.yml`](./.github/workflows/ci.yml) for supported (tested) Ruby versions. +## Test runner setup + +Choose the entrypoint for the test runner that owns the declarations. Each +performs complete global setup for one runner; see its guide for lifecycle and +manual-setup details. + ### RSpec -Extend `WithModel` into RSpec: +```ruby +require "with_model/rspec" +``` + +See the [RSpec guide](docs/rspec.md). + +### Minitest ```ruby -require "with_model" +require "with_model/minitest" +``` -RSpec.configure do |config| - config.extend WithModel -end +See the [Minitest guide](docs/minitest.md). + +### Test::Unit + +```ruby +require "with_model/test_unit" ``` -### minitest/spec +See the [Test::Unit guide](docs/test-unit.md). -Extend `WithModel` into minitest/spec and set the test runner explicitly: +### Advanced and mixed-runner setup + +Plain `require "with_model"` remains framework-neutral. For manual setup, +select a runner and extend its test context directly: ```ruby +require "minitest" require "with_model" WithModel.runner = :minitest +Minitest::Test.extend WithModel +``` -class Minitest::Spec - extend WithModel +RSpec uses `RSpec.configure { |config| config.extend WithModel }`; Test::Unit +uses `Test::Unit::TestCase.extend WithModel`. A declaration can override the +global runner when needed: + +```ruby +with_model :Post, runner: :test_unit do + table end ``` +Loading multiple convenience entrypoints in one process is unsupported because +the last one changes the global runner. For mixed runners, use plain +`with_model`, direct extension, and explicit `runner:` options. + ## Usage After setting up as above, call `with_model` and inside its block pass it a `table` block and a `model` block. @@ -252,7 +283,13 @@ the order they are declared, and destroyed in the reverse order. ## Requirements -See the [gemspec metadata](https://rubygems.org/gems/with_model) for dependency requirements. RSpec and minitest are indirect dependencies, and `with_model` should support any maintained version of both. +See the [gemspec metadata](https://rubygems.org/gems/with_model) for Ruby and +Active Record requirements. RSpec, Minitest, and Test::Unit are optional +consumer framework dependencies; install the framework used by its selected +entrypoint. Requiring a selected entrypoint without its framework installed +raises the framework's ordinary `LoadError`. + +`scope:` is RSpec-only and is silently ignored by Minitest and Test::Unit. ## Thread-safety diff --git a/docs/minitest.md b/docs/minitest.md new file mode 100644 index 0000000..fd3f0fe --- /dev/null +++ b/docs/minitest.md @@ -0,0 +1,45 @@ +# Minitest integration + +Load the Minitest entrypoint before defining tests: + +```ruby +require "with_model/minitest" +``` + +This requires `minitest`, then `with_model`, selects the +`:minitest` runner, and extends `Minitest::Test`. Its class-level DSL is +inherited by `Minitest::Test` subclasses, including `Minitest::Spec` and +`ActiveSupport::TestCase`. The entrypoint does not require or start Minitest's +autorun — your process or test framework owns Minitest startup. + +## Lifecycle + +The Minitest runner creates declared models and tables in `before_setup` and +destroys them in `after_teardown`. This makes them available to ordinary setup +methods and keeps them available through teardown. `scope:` is silently ignored +by Minitest because it is an RSpec-only option. + +## Manual setup + +If another process or tool owns Minitest configuration, configure manually: + +```ruby +require "minitest" +require "with_model" + +WithModel.runner = :minitest +Minitest::Test.extend WithModel +``` + +Minitest is an optional consumer dependency. Requiring `with_model/minitest` +without Minitest installed raises Minitest's ordinary `LoadError`. + +## Mixed runners + +Do not load multiple convenience entrypoints in one process: each changes the +global runner, and the last one wins. For mixed runners, load plain +`with_model`, extend each framework boundary directly, and select a runner per +declaration with `runner:`. + +See the [general DSL reference](../README.md#usage) for declaring models and +tables. diff --git a/docs/rspec.md b/docs/rspec.md new file mode 100644 index 0000000..2dc9dc8 --- /dev/null +++ b/docs/rspec.md @@ -0,0 +1,43 @@ +# RSpec integration + +Load the RSpec entrypoint before defining example groups: + +```ruby +require "with_model/rspec" +``` + +The entrypoint requires `rspec/core` and `with_model`, selects the `:rspec` +runner, and extends RSpec example groups through `RSpec.configure`. It does not +start RSpec; the RSpec command-line runner remains in control. + +## Lifecycle + +`with_model` and `with_table` register RSpec `before` and `after` hooks. Models +and tables are available to examples after setup and are removed after cleanup. +Use `scope:` when RSpec hook scope is needed, including `scope: :all` for a +model needed by `before(:all)`. `scope:` is an RSpec-only option. + +## Manual setup + +For runner-owned setup, require the framework and `with_model` separately: + +```ruby +require "rspec/core" +require "with_model" + +WithModel.runner = :rspec +RSpec.configure { |config| config.extend WithModel } +``` + +RSpec is an optional consumer dependency. Requiring `with_model/rspec` without +RSpec installed raises RSpec's ordinary `LoadError`. + +## Mixed runners + +Do not load multiple convenience entrypoints in one process: each changes the +global runner, and the last one wins. For mixed runners, load plain +`with_model`, extend each framework boundary directly, and select a runner per +declaration with `runner:`. + +See the [general DSL reference](../README.md#usage) for declaring models and +tables. diff --git a/docs/test-unit.md b/docs/test-unit.md new file mode 100644 index 0000000..aa00100 --- /dev/null +++ b/docs/test-unit.md @@ -0,0 +1,73 @@ +# Test::Unit integration + +Load the Test::Unit entrypoint before defining tests: + +```ruby +require "with_model/test_unit" +``` + +The entrypoint requires `test/unit`, then `with_model`, selects the +`:test_unit` runner, and extends `Test::Unit::TestCase`. Test::Unit retains +ownership of autorun and fixture execution. + +## Lifecycle + +`with_model` and `with_table` register Test::Unit fixture callbacks with exact +placement: setup uses `before: :append`, while teardown uses `after: :prepend`. +Declared models and tables therefore create in declaration order before user or +inherited setup methods and callbacks. They destroy in reverse order after user +or inherited teardown methods and callbacks. + +Test::Unit runs teardown when setup raises. `WithModel::Model#destroy` +defensively handles creation that never assigned a model. `Table#destroy` checks +whether its table exists before dropping it, so teardown does not raise solely +because a reconnect replaced an in-memory database or the table otherwise +already disappeared. It does not rescue other database, connection, or drop +errors; those still propagate. This conditional drop check does not promise +generic partial-creation recovery for bare `with_table`. `scope:` is silently +ignored by Test::Unit because it is an RSpec-only option. + +## Errata and concerns + +Establish the Active Record connection before `with_model`'s setup callback +creates tables and models. An ordinary `def setup` runs too late. Connect in +your test helper before test classes load, or register a connection callback +with `setup(before: :prepend)`. + +```ruby +class PostTest < Test::Unit::TestCase + setup(before: :prepend) do + ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:") + end + + with_model :Post do + table + end +end +``` + +## Manual setup + +For runner-owned setup, require Test::Unit and `with_model` separately: + +```ruby +require "test/unit" +require "with_model" + +WithModel.runner = :test_unit +Test::Unit::TestCase.extend WithModel +``` + +Test::Unit is an optional consumer dependency. Requiring +`with_model/test_unit` without Test::Unit installed raises Test::Unit's ordinary +`LoadError`. + +## Mixed runners + +Do not load multiple convenience entrypoints in one process: each changes the +global runner, and the last one wins. For mixed runners, load plain +`with_model`, extend each framework boundary directly, and select a runner per +declaration with `runner:`. + +See the [general DSL reference](../README.md#usage) for declaring models and +tables. diff --git a/with_model.gemspec b/with_model.gemspec index 8ccab57..5c550fe 100644 --- a/with_model.gemspec +++ b/with_model.gemspec @@ -9,7 +9,8 @@ Gem::Specification.new do |spec| spec.authors = ["Case Commons, LLC", "Grant Hutchins", "Andrew Marshall"] spec.email = %w[casecommons-dev@googlegroups.com gems@nertzy.com andrew@johnandrewmarshall.com] spec.homepage = "https://github.com/Casecommons/with_model" - spec.summary = "Dynamically build a model within an RSpec context" + spec.summary = "Dynamically build an Active Record model for " \ + "RSpec, Minitest, and Test::Unit tests" spec.description = spec.summary spec.license = "MIT" spec.metadata["rubygems_mfa_required"] = "true"