From: Caleb Clausen Date: 2010-05-24T11:35:03+09:00 Subject: Re: How do I write an RSpec test to unit-test this interesting metaprogramming code? On 5/23/10, James Wenton 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.