Saturday, July 13, 2013

Templatized / Metaprogramming Testing

We often think of rules/specifications as applying to more than one object.  When I first began learning Ruby, I had followed Hartl's examples in writing tests and discovered that I was doing a lot of copy-and-paste.  That didn't feel right but I didn't know to do.  I wanted to do something like "foreach sign-in state { foreach page { verify page }}".

During the conversion of project to Cabybara 2.0, I noticed that descriptions could be tagged with additional hash key-value pairs.  They had something like:
describe "this test", :type => "feature" do

Interestingly, there is a note in rspec / rspec-core on metadata.  After some playing, I discovered a working design pattern.


  user_states = ["visiting","signed in", "admin"]

  user_states.each do |user_state|
    describe "for #{user_state} users", :user_state => user_state do
      let( :user_state) { example.metadata[:user_state] }

      let(:user) do
        user = nil
        case user_state
        when "signed in"
          user = FactoryGirl.create(:user)
        when "admin"
          user = FactoryGirl.create(:admin)
        end
        user
      end

      before do
        sign_in user unless user.nil?
      end

      all_static_page_names.each do |pg|
        describe pg, :page_name => pg do

          let(:page_name) { example.metadata[:page_name] }
          let(:pa) { PageAttributes.new( page_name) }

          before do
            # puts "          user state #{user_state}"
            pa.path = path_for_static_page( page_name)
            # puts "        pa: #{pa.inspect}"
            visit pa.path
          end

          it { should have_title( pa.title) }
          it { should_not have_title(full_title('Edit user')) }

          it { should have_link('Contact', href: contact_path) }
          it { should have_link('About', href: about_path) }
          
          if( user_state=="visiting") then
            it { should have_link('Sign in', href: signin_path) }
          else
            it { should have_link('Sign out', href: signout_path) }
          end

        end
      end

    end

  end

I have a few helper routines in my utilities. One provides the array of page names, another returns the page attributes for a page. The last returns the path to a page given a page name. I had to keep the path one separate as when I tried to include in the PageAttribute constructor, something was wrong and I couldn't access the *_path variables.

No comments:

Post a Comment