Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rspec ignores default_url_options set in application_controller.rb #255

Closed
adamtao opened this issue Nov 2, 2010 · 32 comments
Closed

Rspec ignores default_url_options set in application_controller.rb #255

adamtao opened this issue Nov 2, 2010 · 32 comments

Comments

@adamtao
Copy link

adamtao commented Nov 2, 2010

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

@dchelimsky
Copy link
Contributor

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

@adamtao
Copy link
Author

adamtao commented Nov 2, 2010

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

@dchelimsky
Copy link
Contributor

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?

@adamtao
Copy link
Author

adamtao commented Nov 4, 2010

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?

@dchelimsky
Copy link
Contributor

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

@adamtao
Copy link
Author

adamtao commented Nov 17, 2010

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.

@dchelimsky
Copy link
Contributor

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

@manuelmeurer
Copy link

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

@adamtao
Copy link
Author

adamtao commented Mar 4, 2011

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.

@manuelmeurer
Copy link

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

@manuelmeurer
Copy link

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

@nickurban
Copy link

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

@phoet
Copy link

phoet commented Nov 24, 2011

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

@shioyama
Copy link

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.

@oelmekki
Copy link

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

@gaganawhad
Copy link

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?

@oelmekki
Copy link

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.

@deivid-rodriguez
Copy link
Contributor

@oelmekki Thanks for that last monkeypatch!

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

@deivid-rodriguez
Copy link
Contributor

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

@oelmekki
Copy link

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).

@deivid-rodriguez
Copy link
Contributor

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. :)

@oelmekki
Copy link

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.

judithhartmann pushed a commit to openHPI/hpi-connect-portal that referenced this issue Dec 6, 2013
@jmuheim
Copy link

jmuheim commented Apr 8, 2014

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?

@JonRowe
Copy link
Member

JonRowe commented Apr 8, 2014

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

@jmuheim
Copy link

jmuheim commented May 4, 2014

@oelmekki Does your fix work for anonymous controller specs, too? I have some functionality in application controller that I'd like to test, e.g.:

describe ApplicationController do
  controller(ApplicationController) do
    def index
      render text: 'Hello World'
    end
  end

  context 'user not logged in (guest)' do
    it 'creates a guest user for a first request' do
      expect {
        get :index
      }.to change { User.guests.count }.from(0).to 1

      expect(User.guests.last).to be_guest
    end
  end
end

This results in errors like this:

  3) ApplicationController user not logged in (guest) skips confirmation for the created guest user
     Failure/Error: get :index
     ActionController::UrlGenerationError:
       No route matches {:action=>"index", :controller=>"anonymous", :locale=>:en}
     # ./app/controllers/application_controller.rb:81:in `ensure_locale'
     # ./spec/controllers/application_controller_spec.rb:20:in `block (3 levels) in <top (required)>'
     # -e:1:in `<main>'

@oelmekki
Copy link

@jmuheim : woops, apologies, I saw your message while I was off and
forgot to get back on it.

Haven't ever used it that way. There is probably specific logic
somewhere that allows to map { action: "index", controller: "anonymous"} to match the controller anyway, should look there.

itsliupeng added a commit to itsliupeng/jianshu-patients that referenced this issue May 24, 2014
When unit testing views the controller is just a stub controller which inherits from ActionController::Base and not
ApplicationController. You need to override this stub if you need special behaviour like default_url_options, e.g:
@deivid-rodriguez
Copy link
Contributor

Hi! So at this point we know this is a Rails issue, but there's not any opened issue in Rails for this. Right?

@igorkasyanchuk
Copy link

module FixLocalesSpecs
  module ::ActionController::TestCase::Behavior
    alias_method :process_without_logging, :process

    def process(action, http_method = 'GET', *args)
      e = Array.wrap(args).compact
      e[0] ||= {}
      e[0].merge!({locale: I18n.locale})
      process_without_logging(action, http_method, *e)
    end
  end
end

This code helped me .... just create spec/support/fix_locales.rb

@ivanxuu
Copy link

ivanxuu commented Jul 31, 2016

For me @igorkasyanchuk solution works the best. Don't forget to require spec/**/*_spec.rb as by default the line Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f } in spec/rails_helper.rb is commented.

@leoplct
Copy link

leoplct commented Nov 22, 2020

Try add this in config/enviroment/test.rb

Rails.application.routes.default_url_options = { locale: 'en' }

@sathibabu-icentris
Copy link

sathibabu-icentris commented Mar 20, 2021

Try add this in config/enviroment/test.rb

Rails.application.routes.default_url_options[:locale]= I18n.default_locale

@digitalmoksha
Copy link

Based on some previous solutions, the thing that currently works best for me in Rails 6 is

[ApplicationController, ActionController::Base].each do |klass|
  klass.class_eval do
    def default_url_options(options = {})
      { locale: I18n.default_locale }.merge(options)
    end
  end
end

in a spec/support/fix_locale.rb file

This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests