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
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ gem "rspec"
gem "ruby-lsp"
gem "sqlite3"
gem "standard"
gem "test-unit"
8 changes: 7 additions & 1 deletion Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,13 @@ RSpec::Core::RakeTask.new
require "minitest/test_task"
Minitest::TestTask.create

require "rake/testtask"
Rake::TestTask.new(:test_unit) do |task|
task.libs << "lib" << "test_unit" << "test"
task.pattern = "test_unit/**/*_test.rb"
end

# standard rake task
require "standard/rake"

task default: %i[spec test standard]
task default: %i[spec test test_unit standard]
7 changes: 6 additions & 1 deletion lib/with_model.rb
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,13 @@ def setup_object(object, scope: nil, runner: nil)
class_eval do
include MiniTestLifeCycle.call(object)
end
when :test_unit
# These options place creation before and destruction after user callbacks,
# preserving declaration and reverse order across inheritance.
setup(before: :append) { object.create }
teardown(after: :prepend) { object.destroy }
else
raise ArgumentError, "Unsupported test runner set, expected :rspec or :minitest"
raise ArgumentError, "Unsupported test runner set, expected :rspec, :minitest, or :test_unit"
end
end
end
186 changes: 186 additions & 0 deletions test_unit/lifecycle_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
# frozen_string_literal: true

require "test_unit_helper"

class TestUnitLifecycleBaseTest < Test::Unit::TestCase
FIXTURE_EVENTS = []

with_model :TestUnitLifecycleParent do
table { |t| t.string "name" }
end

def setup
FIXTURE_EVENTS.clear
assert_kind_of Class, TestUnitLifecycleParent
FIXTURE_EVENTS << :inherited_setup
end

def teardown
assert_kind_of Class, TestUnitLifecycleParent
FIXTURE_EVENTS << :inherited_teardown
end
end

class TestUnitLifecycleTest < TestUnitLifecycleBaseTest
with_model :TestUnitLifecycleChild do
table do |t|
t.references "parent", foreign_key: {to_table: TestUnitLifecycleParent.table_name}
end

model { belongs_to :parent, class_name: TestUnitLifecycleParent.name }
end

def setup
super
assert_kind_of Class, TestUnitLifecycleChild
FIXTURE_EVENTS << :local_setup
end

def teardown
assert_kind_of Class, TestUnitLifecycleChild
FIXTURE_EVENTS << :local_teardown
super
return unless name == "test_models_are_available_through_user_fixtures"

assert_equal [
:inherited_setup,
:local_setup,
:test_body,
:local_teardown,
:inherited_teardown
], FIXTURE_EVENTS
end

def test_models_are_available_through_user_fixtures
parent = TestUnitLifecycleParent.create!(name: "parent")
child = TestUnitLifecycleChild.create!(parent: parent)

assert_equal parent, child.reload.parent
FIXTURE_EVENTS << :test_body
end

def test_model_destruction_unwinds_inheritance_order
parent_test_case = Class.new(Test::Unit::TestCase) do
with_model :TestUnitLifecycleOrderParent do
table
end
end
test_case = Class.new(parent_test_case) do
teardown(after: :prepend) do
assert_false Object.const_defined?(:TestUnitLifecycleOrderChild)
assert_false ActiveRecord::Base.connection.data_source_exists?(child_table_name)
assert_kind_of Class, TestUnitLifecycleOrderParent
assert ActiveRecord::Base.connection.data_source_exists?(parent_table_name)
end

with_model :TestUnitLifecycleOrderChild do
table
end

define_method(:parent_table_name) { TestUnitLifecycleOrderParent.table_name }
define_method(:child_table_name) { @child_table_name }
define_method(:test_lifecycle) { @child_table_name = TestUnitLifecycleOrderChild.table_name }
end

result = Test::Unit::TestResult.new
test_case.new("test_lifecycle").run(result) {}

assert_equal 1, result.run_count
assert_equal 0, result.error_count
end
end

class TestUnitUserHookFailureTest < Test::Unit::TestCase
def test_setup_error_does_not_prevent_model_cleanup
test_case = Class.new(Test::Unit::TestCase) do
class << self
attr_accessor :created_table_name
end

with_model :TestUnitSetupFailureModel do
table
end

def setup
self.class.created_table_name = TestUnitSetupFailureModel.table_name
raise "setup failed"
end

def test_setup_failure
end
end

result = Test::Unit::TestResult.new
test_case.new("test_setup_failure").run(result) {}

assert_hook_failure_cleans_up_model(
result,
RuntimeError,
"setup failed",
:TestUnitSetupFailureModel,
test_case.created_table_name
)
end

def test_teardown_error_does_not_prevent_model_cleanup
test_case = Class.new(Test::Unit::TestCase) do
class << self
attr_accessor :created_table_name
end

with_model :TestUnitTeardownFailureModel do
table
end

def teardown
self.class.created_table_name = TestUnitTeardownFailureModel.table_name
raise "teardown failed"
end

def test_teardown_failure
end
end

result = Test::Unit::TestResult.new
test_case.new("test_teardown_failure").run(result) {}

assert_hook_failure_cleans_up_model(
result,
RuntimeError,
"teardown failed",
:TestUnitTeardownFailureModel,
test_case.created_table_name
)
end

private

def assert_hook_failure_cleans_up_model(result, error_class, message, constant, table_name)
assert_equal 1, result.run_count
assert_equal 1, result.error_count
assert_equal error_class, result.errors.first.exception.class
assert_equal message, result.errors.first.exception.message
assert_false Object.const_defined?(constant)
assert_false ActiveRecord::Base.connection.data_source_exists?(table_name)
end
end

class TestUnitPartialModelCreationFailureTest < Test::Unit::TestCase
def test_preserves_the_setup_error_when_model_creation_does_not_assign_a_model
test_case = Class.new(Test::Unit::TestCase) do
with_model :TestUnitPartiallyCreatedModel, superclass: Object do
table(false)
end

def test_setup_failure
end
end

result = Test::Unit::TestResult.new
test_case.new("test_setup_failure").run(result) {}

assert_equal 1, result.run_count
assert_equal 1, result.error_count
assert_kind_of WithModel::InvalidSuperclass, result.errors.first.exception
end
end
71 changes: 71 additions & 0 deletions test_unit/sti_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# frozen_string_literal: true

require "test_unit_helper"

# Stands in for an application's own model, the usual reason to want single table
# inheritance: its table is created once and outlives every test here, so a
# with_model child's rows cannot be disposed of by dropping the table.
module TestUnitAppModels
class Cupboard < ActiveRecord::Base
end
end

ActiveRecord::Base.connection.create_table TestUnitAppModels::Cupboard.table_name,
force: true do |t|
t.string "type"
t.string "name"
end

# A with_model parent and an STI child in the same test case. The child's
# superclass is resolved per-example, so this only works if the parent is
# created before the child and destroyed after it.
class TestUnitStiTest < Test::Unit::TestCase
with_model :TestUnitVehicle do
table do |t|
t.string "type"
t.string "name"
end
end

with_model :TestUnitTruck, superclass: -> { TestUnitVehicle } do
table(false)
end

with_model :TestUnitChest, superclass: "TestUnitAppModels::Cupboard" do
table(false)
end

def test_the_child_shares_the_parents_table
assert_equal TestUnitVehicle.table_name, TestUnitTruck.table_name
end

def test_the_child_stores_its_own_type
truck = TestUnitTruck.create!(name: "Ford F-150")

assert_equal "TestUnitTruck", truck.reload.type
assert_instance_of TestUnitTruck, TestUnitVehicle.first
end

# Rows in a table the child does not own have to be deleted when it goes away,
# since nothing else will: Cupboard's table is still standing afterwards, and a
# row naming a class that no longer exists makes it unloadable. Both tests write
# one and first insist the table is empty, so whichever test happens to run
# second fails if the rows survived teardown.
def test_the_childs_rows_do_not_outlive_it
assert_empty TestUnitAppModels::Cupboard.all,
"a previous test's rows outlived it"

TestUnitChest.create!(name: "sideboard")

assert_equal 1, TestUnitAppModels::Cupboard.count
end

def test_the_childs_rows_leave_the_superclass_loadable
assert_empty TestUnitAppModels::Cupboard.all,
"a previous test's rows outlived it"

TestUnitChest.create!(name: "wardrobe")

assert_instance_of TestUnitChest, TestUnitAppModels::Cupboard.first
end
end
15 changes: 15 additions & 0 deletions test_unit/test_unit_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# frozen_string_literal: true

$LOAD_PATH.unshift File.expand_path("../lib", __dir__)

require "active_record"
require "test/unit"
require "with_model"

WithModel.runner = :test_unit

Test::Unit::TestCase.extend WithModel

# WithModel requires ActiveRecord::Base.connection to be established.
# If ActiveRecord already has a connection, as in a Rails app, this is unnecessary.
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
58 changes: 58 additions & 0 deletions test_unit/with_model_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# frozen_string_literal: true

require "test_unit_helper"

class TestUnitWithModelTest < Test::Unit::TestCase
with_model :TestUnitBlogPost do
table { |t| t.string "title" }
model { define_method(:fancy_title) { "Title: #{title}" } }
end

with_table :test_unit_widgets do |t|
t.string "name"
end

def test_creates_a_temporary_active_record_model
record = TestUnitBlogPost.create!(title: "New blog post")

assert_equal "New blog post", record.reload.title
end

def test_defines_model_methods
assert_equal "Title: New blog post", TestUnitBlogPost.new(title: "New blog post").fancy_title
end

def test_defines_the_model_constant
assert_kind_of Class, TestUnitBlogPost
end

def test_creates_a_temporary_table
assert_true ActiveRecord::Base.connection.data_source_exists?("test_unit_widgets")
end
end

class TestUnitPerDeclarationRunnerTest < Test::Unit::TestCase
with_model :TestUnitPerDeclarationModel, runner: :test_unit do
table
end

def test_supports_a_test_unit_runner_override
assert_kind_of Class, TestUnitPerDeclarationModel
end
end

class TestUnitRunnerValidationTest < Test::Unit::TestCase
def test_names_all_supported_runners_for_an_unsupported_runner
error = assert_raise(ArgumentError) do
Class.new(Test::Unit::TestCase) do
extend WithModel

with_model :UnsupportedTestUnitModel, runner: :unsupported do
table
end
end
end

assert_equal "Unsupported test runner set, expected :rspec, :minitest, or :test_unit", error.message
end
end