Update: I've realised the error of my ways and formed a neater solution - read about it here

This is a call guidance. My attempts to solve some programming problems normally result in something overly complex.

We're busily working on the next application to make the world a better place. It seems that using sub-domains for routing customer accounts is the thing to do these days and our app is no different.

I've just been setting up this mechanism onto existing core code from the stand-alone application that we're pulling this from. My ApplicationController defines a before_filter that determines the account being accessed from the URL. As such, all of my controller tests need to set up the host once they have setup the relevant test response objects:

def setup
  @controller = DashboardController.new
  @request    = ActionController::TestRequest.new
  @response   = ActionController::TestResponse.new

  @request.host = "curve21.mymagicalapp.com"
  @request.session[:current_user_id] = 1
end

Every 37signals fan-boy app needs a Dashboard right?

OK - so this works. This host and session data is used in 90% of my tests. I could paste this into every test but that's just not DRY. I could make a method called 'setup_host' and stick it in my test helper, but I've already got a bunch of tests setup. Urg. I could paste the method into every test setup - and maybe I should - but I came up with nastier way...

Looking a Test::Unit::TestCase, here's how the actual tests are run:

setup
__send__(@method_name)

The test's 'setup' method is called and then the test method is called via 'send'. I need something in between that. Here's what I did:

In test_helper.rb:

class Test::Unit::TestCase

  # ...

  def __send__(method_name)
    if @request
      @request.host = "curve21.mymagicalapp.com"
      @request.session[:current_user_id] = 1
    end
    super(method_name)
  end
end

I'm overriding send for TestCase. This works. But it doesn't please me completely.

Firstly, Ruby doesn't like me overriding send:

warning: redefining `__send__' may cause serious problem

Secondly, I only really want this to happen on tests that set up @request objects. If I didn't have that 'if' statement, all my unit tests would error.

So what does everyone else think? Is there another way to do this? Should I be hung for my crimes against programming? Or could this work well with minor tweaks?

All answers get a virtual pat on the back.

3 Responses to “Setting up test data after controller setup”

  1. Marcos Says:

    Try something like this:

    class Test::Unit::TestCase

    aliasmethodchain :run, :something

    def runwithsomething(result, &block) #Operations here runwithoutsomething(result, &block) end ... ...

    I tried it but I always get an error saying 'Stack level too deep'. Despite this error (perhaps you can solve it), this approach is more rails-style.

    Bye!

  2. Tom Ward Says:

    Overriding send is a horrible code smell. Needing the host set in 90% of your tests may also be a sign you could refactor a little more.

    I'd suggest looking into mocha (http://mocha.rubyforge.org) to mock out only the things you need before each test.

    Another solution closer to your original would be to create a subclass of Test::Unit::TestCase, override run to perform your extra setup steps (and ignore if @methodname == "defaulttest"), then derive your tests from that class.

  3. Stephen Bartholomew Says:

    Tom: I agree - it doesn't feel right to override send.

    I think a subclass of TestCase feels like a better way to go. The problem is about where I need this information set up. It has to be after the setup of the controller.

    Thinking about it, it might be good to have a functional test case class as most of my controller tests have pretty much the same setup routine...I'll look into that.

    Cheers!

Sorry, comments are closed for this article.