Helper for creating table and model in Rails tests
Here’s a simple method that you can throw into your test_helper.rb
(or wherever helpers go in your test framework) that works correctly as of Rails 7.0 and Ruby 3.2. This method creates your table, returns the model class, and also the teardown proc you can call yourself. The name of the table is thread safe, because it uses process id and thread id by default.
def make_test_model \
suffix: nil,
name: "test_model_#{suffix}_#{$$}_#{Thread.current.object_id}".squeeze('_'),
parent: 'ApplicationRecord',
**table_options,
&block
table_options.with_defaults!(force: true)
ActiveRecord::Base.connection.create_table(name, **table_options, &block)
model = Class.new(parent.constantize) { self.table_name = name }
[model, -> { ActiveRecord::Base.connection.drop_table(name) }]
end
Here’s the usage example for Minitest:
require 'test_helper'
class MyTest < ActiveSupport::TestCase
setup do
@model, @model_teardown = make_test_model(id: :uuid) { |t|
t.string :first_name
t.string :last_name
t.timestamps
}
end
teardown { @model_teardown.call }
test "something" do
model = @model.create(first_name: 'Max')
assert_predicate model, :persisted?
end
end
Here’s the usage example for RSpec (untested, but you get the gist):
RSpec.describe Something do
before do
@model, @model_teardown = make_test_model(id: :uuid) { |t|
t.string :first_name
t.string :last_name
t.timestamps
}
end
after { @model_teardown.call }
it "does something" do
model = @model.create(first_name: 'Max')
expect(model).to be_persisted
end
end
Options
name: 'table_name'
— lets you specify a custom table name, but be mindful that thread safety is now on you.suffix: 'my_suffix'
— lets you specify a custom part of table name, while preserving thread safety.parent: 'MyRecord'
— lets you specify a class to inherit (ApplicationRecord
by default).**table_options
— other options are sent straight tocreate_table
, so stuff likeid: :uuid
will work.&block
— pass the migration block, and you get thet
arg, on which you can declare the table schema as usual.
Model class name
If you need a real constant name for the model, feel free to add this to your setup
:
RealConst = @model
but again, thread-safety is on you.
To use the same unique name as the table, do something like this:
Object.const_set(@model.table_name.classify, @model)
To teardown the constant, use :remove_const
:
Object.send :remove_const, @model.table_name.classify
Opinion on existing solutions
I found 2 gems (temping and with_model) for creating temporary models and tables in rails tests. They felt overcomplicated to me, because they tried to take over setup/teardown, or integrate with RSpec too much. Instead, I’d like to be given tools to make my own setup/teardown in any test framework. These gems also have a lot of code to support various Rails and Ruby versions. I think it’s easier to support a small method in-house.
Code snippets in this post are covered by 0BSD License.