Skip to content
This repository

Rspec ignores default_url_options set in application_controller.rb #255

Closed
adamtao opened this Issue November 02, 2010 · 24 comments
Adam Anderson

Using Rails 3, I have a :scope in routes.rb:

scope "(/:locale)" do

  resources :products

end

In application_controller.rb I set the default :locale

def default_url_options(options={})

  {:locale => "en-US"}

end

In my views, this works when I test in my browser, but fails with Rspec:

<%= link_to product.name, product %>

While running specs I see:

No route matches {:action=>"show", :controller=>"products", :locale=>#&lt;Product id: ...&gt;}

So, Rspec is not using the default_url_options set in application_controller.rb.

Thanks

David Chelimsky
Owner

What kind of spec is this failing in? Controller w/ render_views? View spec?

Adam Anderson

View specs are giving me troubles. I haven't tried it with render_views in controller specs. I'll give it a try.

David Chelimsky
Owner

I'm pretty sure this is a rails issue, not RSpec. For view specs, RSpec wraps ActionView::TestCase. Can you please try one of the RSpec examples that's failing as an ActionView::TestCase and tell me if you get a different result?

Adam Anderson

Sorry, I've tried to figure this out, but I don't know how to test a view without the controller by using ActionView::TestCase. I couldn't find much helpful documentation either. Any hints?

David Chelimsky
Owner
class WidgetShowTest < ActionView::TestCase
  def test_shows_a_widget
    render "widgets/show", :widget => Widget.new(:name => "ACME Widget")
    assert_match /ACME Widget/, rendered
  end
end
Adam Anderson

Dave, it looks like your initial assessment was correct. The same test fails as an ActionView::TestCase. I'll figure out how to pass this to the rails team. Thanks.

David Chelimsky
Owner

I'm going to go ahead and close this then. Please feel free to add comments here as you learn stuff.

Manuel Meurer

Was this reported as a bug to the Rails team? If yes, could somebody post the link? Can't find it...

Adam Anderson

Yes, here is the link:

https://rails.lighthouseapp.com/projects/8994/tickets/5998-actionviewtestcase-does-not-honor-default_url_options-set-in-application_controllerrb

It was recently marked invalid with a code sample of how testing should be done. I haven't had time to evaluate the response in conjunction with rspec.

Manuel Meurer

Thanks, I will try to implement it that way with RSpec.

Manuel Meurer

It worked for me by just monkeypatching the controller to be tested in a before(:all) filter:

class MyController
  def default_url_options(options = {})
    { :locale => params[:locale] }
  end
end
Nick Urban

If you want to change it everywhere, try dropping this into a file in spec/support

class ActionView::TestCase 
  class TestController
    def default_url_options
      {:locale => 'whatever'}
    end
  end
end
Peter Schröder

i needed to dig a little deeper to get all my spec up and running again with rails 3.1.3:

# workaround, to set default locale for ALL spec
class ActionView::TestCase::TestController
  def default_url_options(options={})
    { :locale => I18n.default_locale }
  end
end

class ActionDispatch::Routing::RouteSet
  def default_url_options(options={})
    { :locale => I18n.default_locale }
  end
end
Chris Salzberg

I've been using @phoet's patch for the past year or so, but the other day when I updated rails from 3.2.3 to 3.2.6, suddenly the problem returned. After some digging I found that an upgrade to journey from 1.0.3 to 1.0.4 was part of the problem, but I still had issues in my integration tests.

Specifically, this would fail:

describe "default_url_options test" do
  context 'test' do
    before do 
      I18n.locale = 'en'
      visit homepage_path
    end

    it "switches the locale to ja" do
      I18n.locale = 'ja'
      visit homepage_path
      page.should have_content("サインイン")
    end
  end
end

The locale would revert whatever it was initially set to, making it impossible to change midway through any particular spec. This used to work no problem.

After digging some more, I found what I think is the source of the problem in this commit to rails, which prioritizes the locale passed in through the options hash over whatever is set in default_url_options. As a result, @phoet's second patch above (to ActionDispatch::Routing::RouteSet) has no effect and any dependent specs fail.

To get around this, I replaced both patches above with this one, which solves the problem everywhere:

class ActionDispatch::Routing::RouteSet
  def url_for_with_locale_fix(options)
    url_for_without_locale_fix(options.merge(:locale => I18n.locale))
  end

  alias_method_chain :url_for, :locale_fix
end

I'd be eager to hear if anyone had the same problem when upgrading rails, or if there is something specific to my environment that is triggering this behaviour.

Olivier El Mekki

Thanks @shioyama , that was helpful. A remark, though :

url_for_without_locale_fix(options.merge(:locale => I18n.locale))

using this, you do not actually set a default, but you override locale param after user pass it to route helper. This is not a problem in your case because you use I18n.locale rather than I18n.default_locale and I suppose your code base do not try to redirect to other locale.

To actually emulate #default_url_options, the #merge should be reversed :

class ActionDispatch::Routing::RouteSet
  def url_for_with_locale_fix(options)
    url_for_without_locale_fix({:locale => I18n.default_locale}.merge(options))
  end

  alias_method_chain :url_for, :locale_fix
end
Gagan Awhad

This has been very helpful! Thanks every one who contributed here.

Is there anyway this can be made a default configuration option in rspec-rails so it doesn't feel like we're monkey patching or is this really a rails issue?

Olivier El Mekki

FYI, controller spec patch still work, but the ActionDispatch::Routing::RouteSet patch did not work for me anymore on rails-4 (using rspec-2, capybara and capybara-webkit). Problem was with ActionDispatch::Routing::RouteSet::NamedRouteCollection::UrlHelper#handle_positional_args, which assigns params to hash keys before ActionDispatch::Routing::RouteSet#url_for was called. So, if you have a route /:locale/foos/:id and you call edit_foos_path( @foo ), options hash passed to #url_for has locale: @foo (which override your default locale on merge).

Here is my fix :

class ActionDispatch::Routing::RouteSet::NamedRouteCollection::UrlHelper
  def call(t, args)
    t.url_for(handle_positional_args(t, args, { locale: I18n.default_locale }.merge( @options ), @segment_keys))
  end
end

This overrides ActionDispatch::Routing::RouteSet::NamedRouteCollection::UrlHelper#call with no possibility to use super or chain, so changes in this file should be observed for.

Sorry @gaganawhad , I still can't answer your question. Even after half a day reading and playing with the code, I'm still unsure where default_url_options should be called for capybara.

But please note a Hash is available in your rspec examples :

before :each do
  default_url_options[ :locale ] = 'fr'
end

That's still a bit messy and hardly better than specifying locale in each route generation, though. I found no way to use that through rspec's config.include or something that was satisfying enough as a drop-in replacement for ActionController::Base#default_url_options.

David Rodríguez

@oelmekki Thanks for that last monkeypatch!

It'd be nice to figure out where this issue belongs :confused:

David Rodríguez

I finally gave up with monkeypatching and followed your last suggestion...

config.before(:each, type: :feature) do
  default_url_options[:locale] = I18n.default_locale
end
Olivier El Mekki

Thanks for feedback, @deivid-rodriguez . I'm curious : what problem did
you have with the monkeypatch ? I'm using it since my rails-4
migration, and haven't had any problem. What are the specific
conditions where it does not do the job ?

For reference, here is the exact code I use :

# workaround, to set default locale for ALL spec

class ActionView::TestCase::TestController
  def default_url_options(options={})
    logged_in = options[ :controller ] =~ /admin/ ? 'members' : nil
    { locale: I18n.default_locale, logged_in: logged_in }
  end
end

class ActionDispatch::Routing::RouteSet::NamedRouteCollection::UrlHelper
  def call(t, args)
    logged_in = @options[ :controller ] =~ /admin/ ? 'members' : nil
    t.url_for(handle_positional_args(t, args, { locale: I18n.default_locale, logged_in: logged_in }.merge( @options ), @segment_keys))
  end
end

I have two defaults : the locale and the "logged_in" part, which only
serves for action caching purpose (logged in pages have a few differences
with unknown visitor pages, but using /members in url does not bypass
auth, of course).

David Rodríguez

Nono, your monkeypatch works just fine.

I was having several issues related to this one when upgrading to rails4 (see rails/rails#12178), so at some point in my desperation I decided to remove all locale monkeypatching and use that before(:each) configuration. And found that it works as well. :)

Olivier El Mekki

I was having several issues related to this one when upgrading to rails4 so at some point in my desperation I decided to remove all locale monkeypatching

I know that moment ;)

Ok, thanks for feedback.

Judith Hartmann judithhartmann referenced this issue from a commit in hpi-swt2/hpi-hiwi-portal December 06, 2013
Judith Hartmann applied fix for routes, so the routing in tests will also use the def…
…ault localisation parameters

rspec/rspec-rails#255
c66e645
Joshua Muheim

Thanks for your comments, guys. I found @deivid-rodriguez' before(:each) work around to work best. Still, this issue exists since 3 years, and there doesn't seem to have an "official" solution have emerged yet. I remember having the problem 18 months ago already, and finding this issue here. Today, with Rails 4, it's still the same issue, with a different solution.

This seems a bit half-baked to me. What should happen with an issue like this?

Jon Rowe
Collaborator

@jmuheim As was clarified when this was originally closed, it's a Rails issue, not an RSpec one.

This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.