views:

107

answers:

1

While writing functional tests for a controller, I came across a scenario where I have a before_filter requesting some information from the database that one of my tests requires. I'm using Factory_girl to generate test data but I want to avoid hitting the database when its not explicitly needed. I'd also like to avoid testing my before_filter method here (I plan to test it in a separate test). As I understand, mocking/stubbing is the way to accomplish this.

My question is, what is the best way to mock/stub this method in this scenario.

My before filter method looks for a site in the db based on a subdomain found in the URL and sets an instance variable to be used in the controller:


#application_controller.rb

def load_site_from_subdomain
  @site = Site.first(:conditions => { :subdomain => request.subdomain })
end

My controller that uses this method as a before_filter:


# pages_controller.rb

before_filter :load_site_from_subdomain

def show
  @page = @site.pages.find_by_id_or_slug(params[:id]).first
  respond_to do |format|
    format.html { render_themed_template }
    format.xml  { render :xml => @page }
  end
end

As you can see, it relies on the @site variable to be set (by the before_filter). During testing however, I'd like to have the test assume that @site has been set, and that it has at least 1 associated page (found by @site.pages). I'd like to then test my load_site_from_subdomain method later.

Here is what I have in my test (using Shoulda & Mocha):


context "a GET request to the #show action" do

  setup do
    @page = Factory(:page)
    @site = Factory.build(:site)

    # stub out the @page.site method so it doesn't go 
    # looking in the db for the site record, this is
    # used in this test to add a subdomain to the URL
    # when requesting the page
    @page.stubs(:site).returns(@site)

    # this is where I think I should stub the load_site_from_subdomain
    # method, so the @site variable will still be set
    # in the controller. I'm just not sure how to do that.
    @controller.stubs(:load_site_from_subdomain).returns(@site)

    @request.host = "#{ @page.site.subdomain }.example.com"
    get :show, :id => @page.id
  end

  should assign_to(:site)
  should assign_to(:page)
  should respond_with(:success)

end

This leaves me with an error in my test results telling me that @site is nil.

I feel like I'm going about this the wrong way. I know it would be easy to simply just Factory.create the site so it exists in the db, but as I said earlier, I'd like to reduce the db usage to help keep my tests speedy.

+1  A: 

Try stubbing out 'Site.first' since it the the setting of the @site var that you need to stub and not the returned var from the before_filter.

chris
Yeah, I agree. Providing @site (in your test) by stubbing out the Site.first call also means you are testing your show action with the filter working as expected too.
Shadwell
Brilliant. Thank you! I knew it was going to be something simple. It makes much more sense now.
Christian
In the controller, I used a named scope which I called find_by_id_or_slug. Now it seems that scope doesn't want to work (tests return a no method error), I moved it to a class method in the model and it works fine. I'm OK with that, but any idea why?
Christian
I don't think that Rails allows for dynamic methods with 'or'.
chris
Nope, it's not a dynamic finder, it's a named scope, I've defined it myself in the Page model
Christian