diff --git a/CHANGELOG.md b/CHANGELOG.md index 245ee1d..3d657d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## Main -Nothing so far +#### Additions +* Refactor to support class variables as well ## 0.1.0 diff --git a/README.md b/README.md index 14b8448..e31baf1 100644 --- a/README.md +++ b/README.md @@ -41,13 +41,6 @@ require 'minitest/substitute' ## Usage -This lightweight gem implements features on a "as needed" basis, as of now: - -* substitution of instance variables -* substitution of global variables - -Please [create an issue describing your use case](https://github.com/svoop/minitest-substitute/issues) in case you need more features such as the substitution of class variables or substitution via accessor methods. - ### Block To substitute the value of an instance variable for the duration of a block: @@ -59,11 +52,27 @@ class Config end end -Config.instance_variable_get('@version') # => 1 -with '@version', 2, on: Config do - Config.instance_variable_get('@version') # => 2 +config = Config.new + +config.instance_variable_get('@version') # => 1 +with '@version', 2, on: config do + config.instance_variable_get('@version') # => 2 +end +config.instance_variable_get('@version') # => 1 +``` + +Class variables can be substituted as well: + +```ruby +class Config + @@counter = 0 +end + +Config.class_variable_get('@@counter') # => 0 +with '@@counter', 42, on: Config do + Config.class_variable_get('@@counter') # => 42 end -Config.instance_variable_get('@version') # => 1 +Config.class_variable_get('@@counter') # => 0 ``` Same goes for global variables: diff --git a/lib/minitest/substitute.rb b/lib/minitest/substitute.rb index c565d7b..201fbc9 100644 --- a/lib/minitest/substitute.rb +++ b/lib/minitest/substitute.rb @@ -2,6 +2,7 @@ require_relative 'substitute/version' +require_relative 'substitute/substitutor' require_relative 'substitute/with' require_relative 'substitute/spec' diff --git a/lib/minitest/substitute/spec.rb b/lib/minitest/substitute/spec.rb index d7b278f..52f2e45 100644 --- a/lib/minitest/substitute/spec.rb +++ b/lib/minitest/substitute/spec.rb @@ -3,9 +3,6 @@ require 'minitest/spec' Minitest::Spec::DSL.class_eval do - class Substitutor - include Minitest::Substitute::With - end # Substitute the variable value for the duration of the current description # @@ -14,12 +11,13 @@ class Substitutor # @param on [Object, nil] substitute in the context of this object # @yield temporary substitution value (takes precedence over +substitute+ param) def with(variable, substitute=nil, on: self) + substitute = yield if block_given? + substitutor = Minitest::Substitute::Substitutor.new(variable, substitute, on: on) before do - substitute = yield if block_given? - Substitutor.send(:commit_substitution, variable, substitute, on: on) + substitutor.commit end after do - Substitutor.send(:rollback_substitution, variable, on: on) + substitutor.rollback end end @@ -44,4 +42,5 @@ def after(_type=nil, &block) end end) #.then &:include end + end diff --git a/lib/minitest/substitute/substitutor.rb b/lib/minitest/substitute/substitutor.rb new file mode 100644 index 0000000..99087f3 --- /dev/null +++ b/lib/minitest/substitute/substitutor.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +module Minitest + module Substitute + class Substitutor + + EVAL_METHODS = [:instance_eval, :instance_eval, :class_eval].freeze + + def initialize(variable, substitute, on:) + @variable, @substitute, @on = variable, substitute, on + @original = get + end + + def commit + set @substitute + end + + def rollback + set @original + end + + private + + def eval_method + EVAL_METHODS[@variable.count('@')] + end + + def get + @on.send(eval_method, @variable.to_s) + end + + def set(value) + @on.send(eval_method, "#{@variable} = value") + end + + end + end +end diff --git a/lib/minitest/substitute/with.rb b/lib/minitest/substitute/with.rb index e6f2da6..c48aa7b 100644 --- a/lib/minitest/substitute/with.rb +++ b/lib/minitest/substitute/with.rb @@ -3,10 +3,6 @@ module Minitest module Substitute module With - @original = {} - class << self - attr_accessor :original - end # Substitute the variable value for the duration of the given block # @@ -16,22 +12,13 @@ class << self # @yield block during which the substitution is made # @return [Object] return value of the yielded block def with(variable, substitute, on: self) - commit_substitution(variable, substitute, on: on) + substitutor = Minitest::Substitute::Substitutor.new(variable, substitute, on: on) + substitutor.commit yield.tap do - rollback_substitution(variable, on: on) + substitutor.rollback end end - private - - def commit_substitution(variable, substitute, on:) - Minitest::Substitute::With.original[variable.hash] = on.instance_eval variable.to_s - on.instance_eval "#{variable} = substitute" - end - - def rollback_substitution(variable, on:) - on.instance_eval "#{variable} = Minitest::Substitute::With.original.delete(variable.hash)" - end end end end diff --git a/spec/factory.rb b/spec/factory.rb index c44109e..f398f9f 100644 --- a/spec/factory.rb +++ b/spec/factory.rb @@ -5,6 +5,8 @@ def initialize @version = 1 @released_on = '2023-08-24' end + + @@counter = 0 end $_spec_config_instance = Config.new diff --git a/spec/lib/minitest/substitute/spec_spec.rb b/spec/lib/minitest/substitute/spec_spec.rb index 467a23e..15dd5a0 100644 --- a/spec/lib/minitest/substitute/spec_spec.rb +++ b/spec/lib/minitest/substitute/spec_spec.rb @@ -4,7 +4,7 @@ require_relative '../../../factory' describe :with do - 10.times do # test value reset even though order is random + 10.times do # test rollback even though order is random describe "instance variable" do context 'untouched' do it "returns the original value" do @@ -22,7 +22,25 @@ end end - 10.times do # test value reset even though order is random + 10.times do # test rollback even though order is random + describe "class variable" do + context 'untouched' do + it "returns the original value" do + _(Config.class_variable_get(:@@counter)).must_equal 0 + end + end + + context 'substituted' do + with '@@counter', 42, on: Config + + it "returns the substitute value" do + _(Config.class_variable_get(:@@counter)).must_equal 42 + end + end + end + end + + 10.times do # test rollback even though order is random describe "global variable" do context 'untouched' do it "returns the original value" do @@ -40,7 +58,7 @@ end end - 10.times do # test value reset even though order is random + 10.times do # test rollback even though order is random describe "environment variable" do context 'untouched' do it "returns the original value" do @@ -58,7 +76,7 @@ end end - 10.times do # test value reset even though order is random + 10.times do # test rollback even though order is random describe "multiple substitutions" do context 'untouched' do it "returns the original values" do diff --git a/spec/lib/minitest/substitute/substitutor_spec.rb b/spec/lib/minitest/substitute/substitutor_spec.rb new file mode 100644 index 0000000..0ff1110 --- /dev/null +++ b/spec/lib/minitest/substitute/substitutor_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require_relative '../../../spec_helper' +require_relative '../../../factory' + +describe :eval_method do + subject do + Minitest::Substitute::Substitutor + end + + it "returns instance_eval for instance variables" do + _(subject.new('@version', nil, on: $_spec_config_instance).send(:eval_method)).must_equal :instance_eval + end + + it "returns class_eval for class variables" do + _(subject.new('@@counter', nil, on: Config).send(:eval_method)).must_equal :class_eval + end + + it "returns instance_eval for global variables" do + _(subject.new('$foobar', nil, on: Object).send(:eval_method)).must_equal :instance_eval + end + + it "returns instance_eval for global accessors" do + _(subject.new('ENV', nil, on: Object).send(:eval_method)).must_equal :instance_eval + end +end diff --git a/spec/lib/minitest/substitute/with_spec.rb b/spec/lib/minitest/substitute/with_spec.rb index ac9c849..ee0f1cf 100644 --- a/spec/lib/minitest/substitute/with_spec.rb +++ b/spec/lib/minitest/substitute/with_spec.rb @@ -14,6 +14,16 @@ end end + describe "class variable" do + it "substitutes the value for the duration of a block" do + _(Config.class_variable_get(:@@counter)).must_equal 0 + with '@@counter', 42, on: Config do + _(Config.class_variable_get(:@@counter)).must_equal 42 + end + _(Config.class_variable_get(:@@counter)).must_equal 0 + end + end + describe "global variable" do it "substitutes the value for the duration of a block" do _($_spec_global_variable).must_equal :original