How do I write an RSpec test to unit-test this interesting metaprogramming code?

I'm a little stumped by this problem. Here's some simple code that,
for each argument specified, will add specific get/set methods named
after that argument. If you write `attr_option :foo, :bar`, then you
will see `#foo/foo=` and `#bar/bar=` instance methods on `Config`:

    module Configurator
      class Config
        def initialize()
          @options = {}
        end

        def self.attr_option(*args)
          args.each do |a|
            if not self.method_defined?(a)
              define_method "#{a}" do
                @options[:"#{a}"] ||= {}
              end

              define_method "#{a}=" do |v|
                @options[:"#{a}"] = v
              end
            else
              throw Exception.new("already have attr_option for #{a}")
            end
          end
        end
      end
    end

So far, so good. I want to write some RSpec tests to verify this code
is actually doing what it's supposed to. But there's a problem! If I
invoke `attr_option :foo` in one of the test methods, that method is
now forever defined in Config. So a subsequent test will fail when it
shouldn't, because `foo` is already defined:

      it "should support a specified option" do
        c = Configurator::Config
        c.attr_option :foo
        # ...
      end

      it "should support multiple options" do
        c = Configurator::Config
        c.attr_option :foo, :bar, :baz # Error! :foo already defined
                                         # by a previous test.
        # ...
      end

Is there a way I can give each test an anonymous "clone" of the
`Config` class which is independent of the others?

As I see it, you have several options:

1) remove the exception you are raising at the end of attr_option.
2) intercept and ignore that exception when you call attr_option in your tests.
3) remove the methods you added at the end of each test. Something
like this should work:
     c.send :undef_method, :foo
4) (what you were asking about) make a copy of Configurator::Config
before changing it. This should work:
     c=Configurator::Config.clone

PS: c is an especially unlucky choice for a local variable name, I
have found. If you ever have to run your program under one of the
console-mode debuggers (I do this all the time) it will get confused
with the continue command, which is abbreviated c, often with highly
frustrating results.

···

On 5/23/10, James Wenton <kkaitan@gmail.com> wrote:

I'm a little stumped by this problem. Here's some simple code that,
for each argument specified, will add specific get/set methods named
after that argument. If you write `attr_option :foo, :bar`, then you
will see `#foo/foo=` and `#bar/bar=` instance methods on `Config`:

    module Configurator
      class Config
        def initialize()
          @options = {}
        end

        def self.attr_option(*args)
          args.each do |a|
            if not self.method_defined?(a)
              define_method "#{a}" do
                @options[:"#{a}"] ||= {}
              end

              define_method "#{a}=" do |v|
                @options[:"#{a}"] = v
              end
            else
              throw Exception.new("already have attr_option for #{a}")
            end
          end
        end
      end
    end

So far, so good. I want to write some RSpec tests to verify this code
is actually doing what it's supposed to. But there's a problem! If I
invoke `attr_option :foo` in one of the test methods, that method is
now forever defined in Config. So a subsequent test will fail when it
shouldn't, because `foo` is already defined:

      it "should support a specified option" do
        c = Configurator::Config
        c.attr_option :foo
        # ...
      end

      it "should support multiple options" do
        c = Configurator::Config
        c.attr_option :foo, :bar, :baz # Error! :foo already defined
                                         # by a previous test.
        # ...
      end

Is there a way I can give each test an anonymous "clone" of the
`Config` class which is independent of the others?

I'm a little stumped by this problem. Here's some simple code that,
for each argument specified, will add specific get/set methods named
after that argument. If you write `attr_option :foo, :bar`, then you
will see `#foo/foo=` and `#bar/bar=` instance methods on `Config`:

module Configurator
class Config
def initialize()
@options = {}
end

   def self\.attr\_option\(\*args\)
     args\.each do |a|
       if not self\.method\_defined?\(a\)
         define\_method &quot;\#\{a\}&quot; do
           @options\[:&quot;\#\{a\}&quot;\] ||= \{\}
         end

         define\_method &quot;\#\{a\}=&quot; do |v|
           @options\[:&quot;\#\{a\}&quot;\] = v
         end
       else
         throw Exception\.new\(&quot;already have attr\_option for \#\{a\}&quot;\)
       end
     end
   end
 end

end

So far, so good. I want to write some RSpec tests to verify this code
is actually doing what it's supposed to. But there's a problem! If I
invoke `attr_option :foo` in one of the test methods, that method is
now forever defined in Config. So a subsequent test will fail when it
shouldn't, because `foo` is already defined:

 it &quot;should support a specified option&quot; do
   c = Configurator::Config
   c\.attr\_option :foo
   \# \.\.\.
 end

 it &quot;should support multiple options&quot; do
   c = Configurator::Config
   c\.attr\_option :foo, :bar, :baz   \# Error\! :foo already defined
                                    \# by a previous test\.
   \# \.\.\.
 end

Caleb is right, c is not a good name ;), however

  lambda{ c.attr_option..... }.should raise_error( WhatWasIt)

I call it WhatWasIt because you really should define your own Exception,
e.g.
IllegalMonitorState = Class::new RuntimeError

please take care to subclass RuntimeError, subclassing Exception is
waaaay toooo general.

HTH
R.

···

On Sun, May 23, 2010 at 4:00 PM, James Wenton <kkaitan@gmail.com> wrote:

Is there a way I can give each test an anonymous "clone" of the
`Config` class which is independent of the others?

--
The best way to predict the future is to invent it.
-- Alan Kay

Caleb and Robert are nit-picking... 'c' is a perfectly fine name for a variable in a 4 line test/spec.

The problem you're having is easily solved by using anonymous subclasses:

     it "should support a specified option" do
       c = Class.new(Configurator::Config)
       c.attr_option :foo
       # ...
     end

That makes a throwaway class that has all the same features of the superclass without any of the infectious properties of calling your attr methods on the real thing.

···

On May 23, 2010, at 07:00 , James Wenton wrote:

So far, so good. I want to write some RSpec tests to verify this code
is actually doing what it's supposed to. But there's a problem! If I
invoke `attr_option :foo` in one of the test methods, that method is
now forever defined in Config. So a subsequent test will fail when it
shouldn't, because `foo` is already defined:

     it "should support a specified option" do
       c = Configurator::Config
       c.attr_option :foo
       # ...
     end

     it "should support multiple options" do
       c = Configurator::Config
       c.attr_option :foo, :bar, :baz # Error! :foo already defined
                                        # by a previous test.
       # ...
     end