From: James Wenton on
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?

From: Caleb Clausen on
On 5/23/10, James Wenton <kkaitan(a)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?

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.

From: Robert Dober on
On Sun, May 23, 2010 at 4:00 PM, James Wenton <kkaitan(a)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
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.

>
> 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

From: Ryan Davis on

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

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.