Reputation: 1005
I am building some abstraction into my Rails (5.2) tests as I want to run the same tests multiple times with different parameter sets.
I can successfully create a helper to generate test classes on the fly. This looks like the following, within my test_helper.rb:
class << self
def test_configs(configs: DEFAULT_CONFIGS, &block)
configs.each do |c|
Class.new(ActiveSupport::TestCase) { yield(c) }
end
end
end
I can use this helper in any given test file as follows:
require 'test_helper'
class SampleTest < ActiveSupport::TestCase
test_configs do |c|
test "#{c[:name]} - something is true" do
puts "#{c[:name]}" => # Correctly outputs c[:name]
end
end
end
"#{c[:name]}"
is correctly interpolated for each iteration according to what "config" is passed from the helper. So far so good.
I am having though a problem creating some helper methods that also make use of the variable c
, either within the test_configs
method itself or within single test files.
None of the following works in giving a consistent match between the c
variable that is passed to the test titles and what happens within the tests themselves:
# Approach 1
class SampleTest < ActiveSupport::TestCase
test_configs do |c|
def config_name
"#{c[:name]}" # => undefined local variable or method `c'
end
test "#{c[:name]} - something is true" do
puts "#{config_name}"
end
end
end
# Approach 2
class SampleTest < ActiveSupport::TestCase
test_configs do |c|
define_method("config_name") {
"#{c[:name]}"
}
test "#{c[:name]} - something is true" do
puts "#{config_name}" # => Uses only the last definition
end
end
end
# Approach 3
class << self
def test_configs(configs: DEFAULT_CONFIGS, &block)
configs.each do |c|
define_method "config_name" do # (same with def config_name)
"#{c[:name]}"
end
Class.new(ActiveSupport::TestCase) { yield(c) }
end
end
end
# => undefined local variable or method `config_name'
How do I get to correctly "inject" methods which make use of the passed variable?
Upvotes: 2
Views: 1133
Reputation: 1005
The right approach was similar to the 3rd one in my question, but yield
must be replaced by instance_eval
in order for the code within the block to inherit the context of the new class I am creating:
# test_helper.rb
class ActiveSupport::TestCase
class << self
def test_configs(configs: DEFAULT_CONFIGS, &block)
configs.each do |c|
Class.new(ActiveSupport::TestCase) {
define_singleton_method "config_title" do
"#{c[:name]}".capitalize
end
define_method "config_name" do
"#{c[:name]}"
end
instance_eval(&block)
end
end
end
end
end
# sample_test.rb
require 'test_helper'
class SampleTest < ActiveSupport::TestCase
test_configs do
test "#{config_title} - something is true" do # => Correctly invokes the class method defined above
puts "#{config_name}" # => Correctly invokes the instance method defined above
end
end
end
As you can see, there is also a difference in that class methods must be defined in order to be used within test titles, while instance methods must be defined for being used within tests.
In case c
must be passed to the block (e.g. for defining additional methods within the block itself), instance_exec
should be the way to go: docs.
Upvotes: 1